- Introduced new status field in the attachment table to track processing states: uploading, uploaded, indexing, indexed, upload_failed, and index_failed. - Added progress and error fields to provide detailed information during the attachment processing workflow. - Implemented AttachmentFilter struct to support filtering attachments by status. - Updated SaveAttachment and GetAttachments methods to handle new status and progress information. - Enhanced tests to validate attachment status management and filtering functionality.
1870 lines
52 KiB
Go
1870 lines
52 KiB
Go
package store
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/yaoapp/gou/connector"
|
|
"github.com/yaoapp/xun/capsule"
|
|
"github.com/yaoapp/yao/config"
|
|
"github.com/yaoapp/yao/test"
|
|
)
|
|
|
|
func TestNewXunDefault(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_assistant")
|
|
|
|
err := capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = capsule.Schema().DropTableIfExists("__unit_test_conversation_assistant")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Add a small delay to ensure table is created
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "default",
|
|
Prefix: "__unit_test_conversation_",
|
|
})
|
|
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
// Check history table
|
|
has, err := capsule.Schema().HasTable("__unit_test_conversation_history")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, true, has)
|
|
|
|
// Check chat table
|
|
has, err = capsule.Schema().HasTable("__unit_test_conversation_chat")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, true, has)
|
|
|
|
// Check assistant table
|
|
has, err = capsule.Schema().HasTable("__unit_test_conversation_assistant")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, true, has)
|
|
|
|
// Validate table structure by attempting operations
|
|
// Test history operations
|
|
messages := []map[string]interface{}{
|
|
{"role": "user", "content": "test message"},
|
|
}
|
|
err = store.SaveHistory("test_user", messages, "test_chat", nil)
|
|
assert.Nil(t, err)
|
|
|
|
history, err := store.GetHistory("test_user", "test_chat")
|
|
assert.Nil(t, err)
|
|
assert.NotEmpty(t, history)
|
|
|
|
// Test chat operations
|
|
err = store.UpdateChatTitle("test_user", "test_chat", "Test Chat")
|
|
assert.Nil(t, err)
|
|
|
|
chat, err := store.GetChat("test_user", "test_chat")
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, chat)
|
|
|
|
// Test assistant operations
|
|
assistant := map[string]interface{}{
|
|
"name": "Test Assistant",
|
|
"type": "assistant",
|
|
"connector": "test",
|
|
"description": "Test Description",
|
|
"tags": []string{"test"},
|
|
"mentionable": true,
|
|
"automated": true,
|
|
}
|
|
|
|
id, err := store.SaveAssistant(assistant)
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, id)
|
|
|
|
// Clean up test data
|
|
err = store.DeleteChat("test_user", "test_chat")
|
|
assert.Nil(t, err)
|
|
|
|
err = store.DeleteAssistant(id.(string))
|
|
assert.Nil(t, err)
|
|
}
|
|
|
|
func TestNewXunConnector(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
conn, err := connector.Select("mysql")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
sch, err := conn.Schema()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer sch.DropTableIfExists("__unit_test_conversation_history")
|
|
defer sch.DropTableIfExists("__unit_test_conversation_chat")
|
|
defer sch.DropTableIfExists("__unit_test_conversation_assistant")
|
|
defer sch.DropTableIfExists("__unit_test_conversation_knowledge")
|
|
defer sch.DropTableIfExists("__unit_test_conversation_attachment")
|
|
|
|
sch.DropTableIfExists("__unit_test_conversation_history")
|
|
sch.DropTableIfExists("__unit_test_conversation_chat")
|
|
sch.DropTableIfExists("__unit_test_conversation_assistant")
|
|
sch.DropTableIfExists("__unit_test_conversation_knowledge")
|
|
sch.DropTableIfExists("__unit_test_conversation_attachment")
|
|
|
|
// Add a small delay to ensure table is created
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "mysql",
|
|
Prefix: "__unit_test_conversation_",
|
|
})
|
|
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
// Check history table
|
|
has, err := sch.HasTable("__unit_test_conversation_history")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, true, has)
|
|
|
|
// Check chat table
|
|
has, err = sch.HasTable("__unit_test_conversation_chat")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, true, has)
|
|
|
|
// Check assistant table
|
|
has, err = sch.HasTable("__unit_test_conversation_assistant")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, true, has)
|
|
|
|
// Test basic operations
|
|
messages := []map[string]interface{}{
|
|
{"role": "user", "content": "test message"},
|
|
}
|
|
err = store.SaveHistory("test_user", messages, "test_chat", nil)
|
|
assert.Nil(t, err)
|
|
|
|
history, err := store.GetHistory("test_user", "test_chat")
|
|
assert.Nil(t, err)
|
|
assert.NotEmpty(t, history)
|
|
|
|
err = store.DeleteChat("test_user", "test_chat")
|
|
assert.Nil(t, err)
|
|
}
|
|
|
|
func TestXunSaveAndGetHistory(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
|
|
|
err := capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "default",
|
|
Prefix: "__unit_test_conversation_",
|
|
TTL: 3600,
|
|
})
|
|
|
|
// save the history
|
|
cid := "123456"
|
|
err = store.SaveHistory("123456", []map[string]interface{}{
|
|
{"role": "user", "name": "user1", "content": "hello"},
|
|
{"role": "assistant", "name": "user1", "content": "Hello there, how"},
|
|
}, cid, nil)
|
|
assert.Nil(t, err)
|
|
|
|
// get the history
|
|
data, err := store.GetHistory("123456", cid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, 2, len(data))
|
|
}
|
|
|
|
func TestXunSaveAndGetHistoryWithCID(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
|
|
|
err := capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "default",
|
|
Prefix: "__unit_test_conversation_",
|
|
TTL: 3600,
|
|
})
|
|
|
|
// save the history with specific cid
|
|
sid := "123456"
|
|
cid := "789012"
|
|
assistantID := "test-assistant-1"
|
|
messages := []map[string]interface{}{
|
|
{"role": "user", "name": "user1", "content": "hello"},
|
|
{"role": "assistant", "name": "assistant1", "content": "Hi! How can I help you?"},
|
|
}
|
|
context := map[string]interface{}{
|
|
"assistant_id": assistantID,
|
|
}
|
|
err = store.SaveHistory(sid, messages, cid, context)
|
|
assert.Nil(t, err)
|
|
|
|
// get the history for specific cid
|
|
data, err := store.GetHistory(sid, cid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, 2, len(data))
|
|
|
|
// Verify assistant_id is saved in chat
|
|
chat, err := store.GetChat(sid, cid)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, assistantID, chat.Chat["assistant_id"])
|
|
|
|
// save another message with different cid and assistant
|
|
anotherCID := "345678"
|
|
anotherAssistantID := "test-assistant-2"
|
|
moreMessages := []map[string]interface{}{
|
|
{"role": "user", "name": "user1", "content": "another message"},
|
|
{"role": "assistant", "name": "assistant2", "content": "Hello!"},
|
|
}
|
|
anotherContext := map[string]interface{}{
|
|
"assistant_id": anotherAssistantID,
|
|
}
|
|
err = store.SaveHistory(sid, moreMessages, anotherCID, anotherContext)
|
|
assert.Nil(t, err)
|
|
|
|
// Verify second chat's assistant_id
|
|
chat2, err := store.GetChat(sid, anotherCID)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, anotherAssistantID, chat2.Chat["assistant_id"])
|
|
|
|
// get history for the first cid - should still be 2 messages
|
|
data, err = store.GetHistory(sid, cid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, 2, len(data))
|
|
|
|
// get history for the second cid - should be 2 messages
|
|
data, err = store.GetHistory(sid, anotherCID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, 2, len(data))
|
|
|
|
// get all history for the sid without specifying cid
|
|
allData, err := store.GetHistory(sid, cid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, 2, len(allData))
|
|
}
|
|
|
|
func TestXunGetChats(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_assistant")
|
|
|
|
// Drop tables before test
|
|
err := capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = capsule.Schema().DropTableIfExists("__unit_test_conversation_assistant")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "default",
|
|
Prefix: "__unit_test_conversation_",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create test assistants first
|
|
assistant1 := map[string]interface{}{
|
|
"assistant_id": "test-assistant-1",
|
|
"name": "Test Assistant 1",
|
|
"avatar": "avatar1.png",
|
|
"type": "assistant",
|
|
"connector": "test",
|
|
}
|
|
assistant2 := map[string]interface{}{
|
|
"assistant_id": "test-assistant-2",
|
|
"name": "Test Assistant 2",
|
|
"avatar": "avatar2.png",
|
|
"type": "assistant",
|
|
"connector": "test",
|
|
}
|
|
_, err = store.SaveAssistant(assistant1)
|
|
assert.Nil(t, err)
|
|
_, err = store.SaveAssistant(assistant2)
|
|
assert.Nil(t, err)
|
|
|
|
// Save some test chats
|
|
sid := "test_user"
|
|
messages := []map[string]interface{}{
|
|
{"role": "user", "content": "test message"},
|
|
}
|
|
|
|
// Create chats with different dates and assistants
|
|
for i := 0; i < 5; i++ {
|
|
chatID := fmt.Sprintf("chat_%d", i)
|
|
title := fmt.Sprintf("Test Chat %d", i)
|
|
var context map[string]interface{}
|
|
|
|
// Alternate between having assistant and no assistant
|
|
if i%2 == 0 {
|
|
context = map[string]interface{}{
|
|
"assistant_id": "test-assistant-1",
|
|
}
|
|
} else if i%3 == 0 {
|
|
context = map[string]interface{}{
|
|
"assistant_id": "test-assistant-2",
|
|
}
|
|
}
|
|
|
|
// Save history first to create the chat
|
|
err = store.SaveHistory(sid, messages, chatID, context)
|
|
assert.Nil(t, err)
|
|
|
|
// Update the chat title
|
|
err = store.UpdateChatTitle(sid, chatID, title)
|
|
assert.Nil(t, err)
|
|
|
|
// Verify chat was created with correct assistant info
|
|
chat, err := store.GetChat(sid, chatID)
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, chat)
|
|
assert.Equal(t, chatID, chat.Chat["chat_id"])
|
|
assert.Equal(t, title, chat.Chat["title"])
|
|
|
|
if i%2 == 0 {
|
|
assert.Equal(t, "test-assistant-1", chat.Chat["assistant_id"])
|
|
assert.Equal(t, "Test Assistant 1", chat.Chat["assistant_name"])
|
|
assert.Equal(t, "avatar1.png", chat.Chat["assistant_avatar"])
|
|
} else if i%3 == 0 {
|
|
assert.Equal(t, "test-assistant-2", chat.Chat["assistant_id"])
|
|
assert.Equal(t, "Test Assistant 2", chat.Chat["assistant_name"])
|
|
assert.Equal(t, "avatar2.png", chat.Chat["assistant_avatar"])
|
|
} else {
|
|
assert.Nil(t, chat.Chat["assistant_id"])
|
|
assert.Nil(t, chat.Chat["assistant_name"])
|
|
assert.Nil(t, chat.Chat["assistant_avatar"])
|
|
}
|
|
}
|
|
|
|
// Test GetChats
|
|
filter := ChatFilter{
|
|
PageSize: 10,
|
|
Order: "desc",
|
|
}
|
|
groups, err := store.GetChats(sid, filter)
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, groups)
|
|
assert.Greater(t, len(groups.Groups), 0)
|
|
|
|
// Verify assistant information in chat list
|
|
for _, group := range groups.Groups {
|
|
for _, chat := range group.Chats {
|
|
if assistantID, ok := chat["assistant_id"].(string); ok && assistantID != "" {
|
|
if assistantID == "test-assistant-1" {
|
|
assert.Equal(t, "Test Assistant 1", chat["assistant_name"])
|
|
assert.Equal(t, "avatar1.png", chat["assistant_avatar"])
|
|
} else if assistantID == "test-assistant-2" {
|
|
assert.Equal(t, "Test Assistant 2", chat["assistant_name"])
|
|
assert.Equal(t, "avatar2.png", chat["assistant_avatar"])
|
|
}
|
|
} else {
|
|
assert.Nil(t, chat["assistant_name"])
|
|
assert.Nil(t, chat["assistant_avatar"])
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test with keywords
|
|
filter.Keywords = "test"
|
|
groups, err = store.GetChats(sid, filter)
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, len(groups.Groups), 0)
|
|
}
|
|
|
|
func TestXunDeleteChat(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "default",
|
|
Prefix: "__unit_test_conversation_",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a test chat
|
|
sid := "test_user"
|
|
cid := "test_chat"
|
|
messages := []map[string]interface{}{
|
|
{"role": "user", "content": "test message"},
|
|
}
|
|
|
|
// Save the chat and history
|
|
err = store.SaveHistory(sid, messages, cid, nil)
|
|
assert.Nil(t, err)
|
|
|
|
// Verify chat exists
|
|
chat, err := store.GetChat(sid, cid)
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, chat)
|
|
|
|
// Delete the chat
|
|
err = store.DeleteChat(sid, cid)
|
|
assert.Nil(t, err)
|
|
|
|
// Verify chat is deleted
|
|
chat, err = store.GetChat(sid, cid)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, (*ChatInfo)(nil), chat)
|
|
}
|
|
|
|
func TestXunDeleteAllChats(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "default",
|
|
Prefix: "__unit_test_conversation_",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create multiple test chats
|
|
sid := "test_user"
|
|
messages := []map[string]interface{}{
|
|
{"role": "user", "content": "test message"},
|
|
}
|
|
|
|
// Save multiple chats
|
|
for i := 0; i < 3; i++ {
|
|
cid := fmt.Sprintf("test_chat_%d", i)
|
|
err = store.SaveHistory(sid, messages, cid, nil)
|
|
assert.Nil(t, err)
|
|
}
|
|
|
|
// Verify chats exist
|
|
response, err := store.GetChats(sid, ChatFilter{})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, response.Total, int64(0))
|
|
|
|
// Delete all chats
|
|
err = store.DeleteAllChats(sid)
|
|
assert.Nil(t, err)
|
|
|
|
// Verify all chats are deleted
|
|
response, err = store.GetChats(sid, ChatFilter{})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, int64(0), response.Total)
|
|
}
|
|
|
|
func TestXunAssistantCRUD(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_assistant")
|
|
|
|
// Drop assistant table before test
|
|
err := capsule.Schema().DropTableIfExists("__unit_test_conversation_assistant")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Add a small delay to ensure table is created
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "default",
|
|
Prefix: "__unit_test_conversation_",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Clean up any existing data
|
|
_, err = store.DeleteAssistants(AssistantFilter{})
|
|
assert.Nil(t, err)
|
|
|
|
// Test case 1: JSON fields as strings
|
|
tagsJSON := `["tag1", "tag2", "tag3"]`
|
|
optionsJSON := `{"model": "gpt-4"}`
|
|
placeholderJSON := `{"title": "Test Title", "description": "Test Description", "prompts": ["prompt1", "prompt2"]}`
|
|
assistant := map[string]interface{}{
|
|
"name": "Test Assistant",
|
|
"type": "assistant",
|
|
"avatar": "https://example.com/avatar.png",
|
|
"connector": "openai",
|
|
"description": "Test Description",
|
|
"path": "/assistants/test",
|
|
"sort": 100,
|
|
"built_in": true,
|
|
"tags": tagsJSON,
|
|
"options": optionsJSON,
|
|
"placeholder": placeholderJSON,
|
|
"mentionable": true,
|
|
"automated": true,
|
|
}
|
|
|
|
// Test SaveAssistant (Create) with string JSON
|
|
v, err := store.SaveAssistant(assistant)
|
|
assert.Nil(t, err)
|
|
assistantID := v.(string)
|
|
assert.NotEmpty(t, assistantID)
|
|
|
|
// Test GetAssistant for the first assistant
|
|
assistantData, err := store.GetAssistant(assistantID)
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, assistantData)
|
|
assert.Equal(t, "Test Assistant", assistantData["name"])
|
|
assert.Equal(t, "assistant", assistantData["type"])
|
|
assert.Equal(t, "https://example.com/avatar.png", assistantData["avatar"])
|
|
assert.Equal(t, "openai", assistantData["connector"])
|
|
assert.Equal(t, "Test Description", assistantData["description"])
|
|
assert.Equal(t, "/assistants/test", assistantData["path"])
|
|
assert.Equal(t, int64(100), assistantData["sort"])
|
|
assert.Equal(t, int64(1), assistantData["built_in"])
|
|
assert.Equal(t, []interface{}{"tag1", "tag2", "tag3"}, assistantData["tags"])
|
|
assert.Equal(t, map[string]interface{}{"model": "gpt-4"}, assistantData["options"])
|
|
assert.Equal(t, map[string]interface{}{
|
|
"title": "Test Title",
|
|
"description": "Test Description",
|
|
"prompts": []interface{}{"prompt1", "prompt2"},
|
|
}, assistantData["placeholder"])
|
|
assert.Equal(t, int64(1), assistantData["mentionable"])
|
|
assert.Equal(t, int64(1), assistantData["automated"])
|
|
|
|
// Test case 2: JSON fields as native types
|
|
assistant2 := map[string]interface{}{
|
|
"name": "Test Assistant 2",
|
|
"type": "assistant",
|
|
"avatar": "https://example.com/avatar2.png",
|
|
"connector": "openai",
|
|
"description": "Test Description 2",
|
|
"path": "/assistants/test2",
|
|
"sort": 200,
|
|
"built_in": false,
|
|
"tags": []string{"tag1", "tag2", "tag3"},
|
|
"options": map[string]interface{}{"model": "gpt-4"},
|
|
"prompts": []string{"prompt1", "prompt2"},
|
|
"workflow": []string{"flow1", "flow2"},
|
|
"knowledge": []string{"file1", "file2"},
|
|
"tools": []map[string]interface{}{{"name": "tool1"}, {"name": "tool2"}},
|
|
"permissions": map[string]interface{}{"read": true, "write": true},
|
|
"placeholder": map[string]interface{}{
|
|
"title": "Test Title 2",
|
|
"description": "Test Description 2",
|
|
"prompts": []string{"prompt3", "prompt4"},
|
|
},
|
|
"mentionable": true,
|
|
"automated": true,
|
|
}
|
|
|
|
// Test SaveAssistant (Create) with native types
|
|
v, err = store.SaveAssistant(assistant2)
|
|
assert.Nil(t, err)
|
|
assistant2ID := v.(string)
|
|
assert.NotEmpty(t, assistant2ID)
|
|
|
|
// Test case 3: Test with nil JSON fields
|
|
assistant3 := map[string]interface{}{
|
|
"name": "Test Assistant 3",
|
|
"type": "assistant",
|
|
"connector": "openai",
|
|
"description": "Test Description 3",
|
|
"path": nil,
|
|
"sort": 9999,
|
|
"built_in": false,
|
|
"tags": nil,
|
|
"options": nil,
|
|
"prompts": nil,
|
|
"workflow": nil,
|
|
"knowledge": nil,
|
|
"tools": nil,
|
|
"permissions": nil,
|
|
"placeholder": nil,
|
|
"mentionable": true,
|
|
"automated": true,
|
|
}
|
|
|
|
// Test SaveAssistant (Create) with nil fields
|
|
v, err = store.SaveAssistant(assistant3)
|
|
assert.Nil(t, err)
|
|
assistant3ID := v.(string)
|
|
assert.NotEmpty(t, assistant3ID)
|
|
|
|
// Test GetAssistant for the third assistant
|
|
assistant3Data, err := store.GetAssistant(assistant3ID)
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, assistant3Data)
|
|
assert.Equal(t, "Test Assistant 3", assistant3Data["name"])
|
|
assert.Nil(t, assistant3Data["tags"])
|
|
assert.Nil(t, assistant3Data["options"])
|
|
assert.Nil(t, assistant3Data["prompts"])
|
|
assert.Nil(t, assistant3Data["workflow"])
|
|
assert.Nil(t, assistant3Data["knowledge"])
|
|
assert.Nil(t, assistant3Data["tools"])
|
|
assert.Nil(t, assistant3Data["permissions"])
|
|
assert.Nil(t, assistant3Data["placeholder"])
|
|
assert.Equal(t, int64(1), assistant3Data["mentionable"])
|
|
assert.Equal(t, int64(1), assistant3Data["automated"])
|
|
|
|
// Test GetAssistant with non-existent ID
|
|
nonExistentData, err := store.GetAssistant("non-existent-id")
|
|
assert.Error(t, err)
|
|
assert.Nil(t, nonExistentData)
|
|
assert.Contains(t, err.Error(), "is empty")
|
|
|
|
// Test GetAssistants to verify JSON fields are properly stored
|
|
resp, err := store.GetAssistants(AssistantFilter{})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 3, len(resp.Data))
|
|
|
|
// Clean up all test data
|
|
_, err = store.DeleteAssistants(AssistantFilter{})
|
|
assert.Nil(t, err)
|
|
|
|
// Verify cleanup
|
|
resp, err = store.GetAssistants(AssistantFilter{})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, len(resp.Data))
|
|
}
|
|
|
|
func TestXunAssistantPagination(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_assistant")
|
|
|
|
// Drop assistant table before test
|
|
err := capsule.Schema().DropTableIfExists("__unit_test_conversation_assistant")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Add a small delay to ensure table is created
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "default",
|
|
Prefix: "__unit_test_conversation_",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create test data for filtering tests
|
|
testAssistants := []map[string]interface{}{}
|
|
for i := 0; i < 25; i++ {
|
|
assistant := map[string]interface{}{
|
|
"name": fmt.Sprintf("Filter Test Assistant %d", i),
|
|
"type": "assistant",
|
|
"connector": fmt.Sprintf("connector%d", i%3),
|
|
"description": fmt.Sprintf("Filter Test Description %d", i),
|
|
"tags": []string{fmt.Sprintf("tag%d", i%5)},
|
|
"built_in": i%2 == 0,
|
|
"mentionable": i%2 == 0,
|
|
"automated": i%3 == 0,
|
|
"sort": 9999 - i,
|
|
}
|
|
id, err := store.SaveAssistant(assistant)
|
|
assert.Nil(t, err)
|
|
assistant["assistant_id"] = id
|
|
testAssistants = append(testAssistants, assistant)
|
|
}
|
|
|
|
// Get first assistant ID for later use
|
|
firstAssistantID := testAssistants[0]["assistant_id"].(string)
|
|
|
|
// Test filtering with assistantIDs
|
|
assistantIDs := []string{firstAssistantID}
|
|
if len(testAssistants) > 1 {
|
|
assistantIDs = append(assistantIDs, testAssistants[1]["assistant_id"].(string))
|
|
}
|
|
|
|
// Test multiple assistant_ids
|
|
resp, err := store.GetAssistants(AssistantFilter{
|
|
AssistantIDs: assistantIDs,
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, len(assistantIDs), len(resp.Data))
|
|
for _, assistant := range resp.Data {
|
|
found := false
|
|
for _, id := range assistantIDs {
|
|
if assistant["assistant_id"] == id {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, found, "Assistant ID should be in the requested list")
|
|
}
|
|
|
|
// Test assistantIDs with other filters
|
|
resp, err = store.GetAssistants(AssistantFilter{
|
|
AssistantIDs: assistantIDs,
|
|
Select: []string{"name", "assistant_id", "description"},
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, len(assistantIDs), len(resp.Data))
|
|
// Verify only selected fields are returned
|
|
for _, item := range resp.Data {
|
|
assert.Contains(t, item, "name")
|
|
assert.Contains(t, item, "assistant_id")
|
|
assert.Contains(t, item, "description")
|
|
assert.NotContains(t, item, "tags")
|
|
assert.NotContains(t, item, "options")
|
|
}
|
|
|
|
// Test filtering with select fields
|
|
resp, err = store.GetAssistants(AssistantFilter{
|
|
Select: []string{"name", "description", "tags"},
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 10, len(resp.Data))
|
|
|
|
// Test filtering with select fields and other filters combined
|
|
resp, err = store.GetAssistants(AssistantFilter{
|
|
Tags: []string{"tag0"},
|
|
Keywords: "Filter Test",
|
|
Select: []string{"name", "tags"},
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, len(resp.Data), 0)
|
|
|
|
// Test combined filters
|
|
mentionableTrue := true
|
|
automatedTrue := true
|
|
resp, err = store.GetAssistants(AssistantFilter{
|
|
Tags: []string{"tag0"},
|
|
Keywords: "Filter Test",
|
|
Connector: "connector0",
|
|
Mentionable: &mentionableTrue,
|
|
Automated: &automatedTrue,
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
|
|
// Now test the delete operations
|
|
// Test delete by connector
|
|
var count int64
|
|
count, err = store.DeleteAssistants(AssistantFilter{
|
|
Connector: "connector0",
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, count, int64(0))
|
|
|
|
// Verify deletion
|
|
resp, err = store.GetAssistants(AssistantFilter{
|
|
Connector: "connector0",
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, len(resp.Data))
|
|
|
|
// Test delete by built_in status
|
|
builtInTrue := true
|
|
count, err = store.DeleteAssistants(AssistantFilter{
|
|
BuiltIn: &builtInTrue,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, count, int64(0))
|
|
|
|
// Verify deletion
|
|
resp, err = store.GetAssistants(AssistantFilter{
|
|
BuiltIn: &builtInTrue,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, len(resp.Data))
|
|
|
|
// Test delete by tags
|
|
count, err = store.DeleteAssistants(AssistantFilter{
|
|
Tags: []string{"tag1"},
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, count, int64(0))
|
|
|
|
// Verify deletion
|
|
resp, err = store.GetAssistants(AssistantFilter{
|
|
Tags: []string{"tag1"},
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, len(resp.Data))
|
|
|
|
// Test delete by keywords
|
|
count, err = store.DeleteAssistants(AssistantFilter{
|
|
Keywords: "Filter Test",
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, count, int64(0))
|
|
|
|
// Verify all assistants are deleted
|
|
resp, err = store.GetAssistants(AssistantFilter{})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, len(resp.Data))
|
|
|
|
// Test delete by assistantIDs
|
|
// First create some test assistants
|
|
testIDs := []string{}
|
|
for i := 0; i < 3; i++ {
|
|
assistant := map[string]interface{}{
|
|
"name": fmt.Sprintf("AssistantIDs Test Assistant %d", i),
|
|
"type": "assistant",
|
|
"connector": "test",
|
|
"description": fmt.Sprintf("AssistantIDs Test Description %d", i),
|
|
"tags": []string{"test-tag"},
|
|
"built_in": false,
|
|
"mentionable": true,
|
|
"automated": true,
|
|
}
|
|
id, err := store.SaveAssistant(assistant)
|
|
assert.Nil(t, err)
|
|
testIDs = append(testIDs, id.(string))
|
|
}
|
|
|
|
// Delete by assistantIDs
|
|
count, err = store.DeleteAssistants(AssistantFilter{
|
|
AssistantIDs: testIDs,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, int64(len(testIDs)), count)
|
|
|
|
// Verify deletion
|
|
resp, err = store.GetAssistants(AssistantFilter{
|
|
AssistantIDs: testIDs,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, len(resp.Data))
|
|
|
|
// Verify all assistants are deleted
|
|
resp, err = store.GetAssistants(AssistantFilter{})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, len(resp.Data))
|
|
}
|
|
|
|
func TestGetAssistantTags(t *testing.T) {
|
|
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_assistant")
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "default",
|
|
Prefix: "__unit_test_conversation_",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create test assistants with tags
|
|
assistants := []map[string]interface{}{
|
|
{
|
|
"assistant_id": "test-assistant-1",
|
|
"type": "assistant",
|
|
"connector": "test",
|
|
"tags": []string{"tag1", "tag2"},
|
|
"name": "Test Assistant 1",
|
|
},
|
|
{
|
|
"assistant_id": "test-assistant-2",
|
|
"type": "assistant",
|
|
"connector": "test",
|
|
"tags": []string{"tag2", "tag3"},
|
|
"name": "Test Assistant 2",
|
|
},
|
|
{
|
|
"assistant_id": "test-assistant-3",
|
|
"type": "assistant",
|
|
"connector": "test",
|
|
"tags": []string{"tag1", "tag3", "tag4"},
|
|
"name": "Test Assistant 3",
|
|
},
|
|
}
|
|
|
|
// Save test assistants
|
|
for _, assistant := range assistants {
|
|
_, err := store.SaveAssistant(assistant)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Get tags
|
|
tags, err := store.GetAssistantTags()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Verify results
|
|
expectedTags := map[string]bool{
|
|
"tag1": true,
|
|
"tag2": true,
|
|
"tag3": true,
|
|
"tag4": true,
|
|
}
|
|
|
|
if len(tags) != len(expectedTags) {
|
|
t.Errorf("Expected %d tags, got %d", len(expectedTags), len(tags))
|
|
}
|
|
|
|
for _, tag := range tags {
|
|
value := tag.Value
|
|
if !expectedTags[value] {
|
|
t.Errorf("Unexpected tag found: %s", tag)
|
|
}
|
|
}
|
|
|
|
// Cleanup
|
|
for _, assistant := range assistants {
|
|
err := store.DeleteAssistant(assistant["assistant_id"].(string))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestXunSaveAndGetHistoryWithSilent(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
|
|
|
err := capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "default",
|
|
Prefix: "__unit_test_conversation_",
|
|
TTL: 3600,
|
|
})
|
|
|
|
// save the history with silent messages
|
|
sid := "123456"
|
|
cid := "silent_test"
|
|
|
|
// First save regular messages
|
|
messages := []map[string]interface{}{
|
|
{"role": "user", "name": "user1", "content": "hello"},
|
|
{"role": "assistant", "name": "assistant1", "content": "Hi! How can I help you?"},
|
|
}
|
|
context := map[string]interface{}{
|
|
"assistant_id": "test-assistant-1",
|
|
}
|
|
err = store.SaveHistory(sid, messages, cid, context)
|
|
assert.Nil(t, err)
|
|
|
|
// Then save silent messages
|
|
silentMessages := []map[string]interface{}{
|
|
{"role": "user", "name": "user1", "content": "silent message"},
|
|
{"role": "assistant", "name": "assistant1", "content": "This is a silent response"},
|
|
}
|
|
silentContext := map[string]interface{}{
|
|
"assistant_id": "test-assistant-1",
|
|
"silent": true,
|
|
}
|
|
err = store.SaveHistory(sid, silentMessages, cid, silentContext)
|
|
assert.Nil(t, err)
|
|
|
|
// Get history without filter (should only return non-silent messages)
|
|
data, err := store.GetHistory(sid, cid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, 2, len(data))
|
|
for _, msg := range data {
|
|
// Check if silent is false, handling different types
|
|
isSilent := false
|
|
switch v := msg["silent"].(type) {
|
|
case bool:
|
|
isSilent = v
|
|
case int:
|
|
isSilent = v != 0
|
|
case int64:
|
|
isSilent = v != 0
|
|
case float64:
|
|
isSilent = v != 0
|
|
}
|
|
assert.False(t, isSilent, "message should not be silent")
|
|
}
|
|
|
|
// Get history with silent=true filter (should return all messages)
|
|
silentTrue := true
|
|
filter := ChatFilter{
|
|
Silent: &silentTrue,
|
|
}
|
|
allData, err := store.GetHistoryWithFilter(sid, cid, filter)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, 4, len(allData))
|
|
|
|
// Count silent messages
|
|
silentCount := 0
|
|
for _, msg := range allData {
|
|
// Check if silent is true, handling different types
|
|
isSilent := false
|
|
switch v := msg["silent"].(type) {
|
|
case bool:
|
|
isSilent = v
|
|
case int:
|
|
isSilent = v != 0
|
|
case int64:
|
|
isSilent = v != 0
|
|
case float64:
|
|
isSilent = v != 0
|
|
}
|
|
if isSilent {
|
|
silentCount++
|
|
}
|
|
}
|
|
assert.Equal(t, 2, silentCount)
|
|
|
|
// Get chat with filter (should include silent messages)
|
|
chat, err := store.GetChatWithFilter(sid, cid, filter)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 4, len(chat.History))
|
|
|
|
// Get chat without filter (should exclude silent messages)
|
|
chatNoSilent, err := store.GetChat(sid, cid)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 2, len(chatNoSilent.History))
|
|
}
|
|
|
|
func TestXunGetChatsWithSilent(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_assistant")
|
|
|
|
// Drop tables before test
|
|
err := capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = capsule.Schema().DropTableIfExists("__unit_test_conversation_assistant")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "default",
|
|
Prefix: "__unit_test_conversation_",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create test assistant
|
|
assistant := map[string]interface{}{
|
|
"assistant_id": "test-assistant-1",
|
|
"name": "Test Assistant 1",
|
|
"avatar": "avatar1.png",
|
|
"type": "assistant",
|
|
"connector": "test",
|
|
}
|
|
_, err = store.SaveAssistant(assistant)
|
|
assert.Nil(t, err)
|
|
|
|
// Save some test chats
|
|
sid := "test_user"
|
|
messages := []map[string]interface{}{
|
|
{"role": "user", "content": "test message"},
|
|
}
|
|
|
|
// Create regular chats
|
|
for i := 0; i < 3; i++ {
|
|
chatID := fmt.Sprintf("regular_chat_%d", i)
|
|
title := fmt.Sprintf("Regular Chat %d", i)
|
|
context := map[string]interface{}{
|
|
"assistant_id": "test-assistant-1",
|
|
"silent": false,
|
|
}
|
|
|
|
// Save history to create the chat
|
|
err = store.SaveHistory(sid, messages, chatID, context)
|
|
assert.Nil(t, err)
|
|
|
|
// Update the chat title
|
|
err = store.UpdateChatTitle(sid, chatID, title)
|
|
assert.Nil(t, err)
|
|
}
|
|
|
|
// Create silent chats
|
|
for i := 0; i < 2; i++ {
|
|
chatID := fmt.Sprintf("silent_chat_%d", i)
|
|
title := fmt.Sprintf("Silent Chat %d", i)
|
|
context := map[string]interface{}{
|
|
"assistant_id": "test-assistant-1",
|
|
"silent": true,
|
|
}
|
|
|
|
// Save history to create the chat
|
|
err = store.SaveHistory(sid, messages, chatID, context)
|
|
assert.Nil(t, err)
|
|
|
|
// Update the chat title
|
|
err = store.UpdateChatTitle(sid, chatID, title)
|
|
assert.Nil(t, err)
|
|
}
|
|
|
|
// Test GetChats with default filter (should exclude silent chats)
|
|
defaultFilter := ChatFilter{
|
|
PageSize: 10,
|
|
Order: "desc",
|
|
}
|
|
defaultGroups, err := store.GetChats(sid, defaultFilter)
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, defaultGroups)
|
|
|
|
// Count total chats in all groups
|
|
totalDefaultChats := 0
|
|
for _, group := range defaultGroups.Groups {
|
|
totalDefaultChats += len(group.Chats)
|
|
}
|
|
assert.Equal(t, 3, totalDefaultChats, "Default filter should only return non-silent chats")
|
|
|
|
// Test GetChats with silent=true filter (should include all chats)
|
|
silentTrue := true
|
|
silentFilter := ChatFilter{
|
|
PageSize: 10,
|
|
Order: "desc",
|
|
Silent: &silentTrue,
|
|
}
|
|
silentGroups, err := store.GetChats(sid, silentFilter)
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, silentGroups)
|
|
|
|
// Count total chats in all groups
|
|
totalSilentChats := 0
|
|
for _, group := range silentGroups.Groups {
|
|
totalSilentChats += len(group.Chats)
|
|
}
|
|
assert.Equal(t, 5, totalSilentChats, "Silent filter should return all chats")
|
|
|
|
// Test GetChats with silent=false filter (should only include non-silent chats)
|
|
silentFalse := false
|
|
nonSilentFilter := ChatFilter{
|
|
PageSize: 10,
|
|
Order: "desc",
|
|
Silent: &silentFalse,
|
|
}
|
|
nonSilentGroups, err := store.GetChats(sid, nonSilentFilter)
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, nonSilentGroups)
|
|
|
|
// Count total chats in all groups
|
|
totalNonSilentChats := 0
|
|
for _, group := range nonSilentGroups.Groups {
|
|
totalNonSilentChats += len(group.Chats)
|
|
}
|
|
assert.Equal(t, 3, totalNonSilentChats, "Non-silent filter should only return non-silent chats")
|
|
}
|
|
|
|
func TestXunAttachmentCRUD(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_attachment")
|
|
|
|
// Drop attachment table before test
|
|
err := capsule.Schema().DropTableIfExists("__unit_test_conversation_attachment")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Add a small delay to ensure table is created
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "default",
|
|
Prefix: "__unit_test_conversation_",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Clean up any existing data
|
|
_, err = store.DeleteAttachments(AttachmentFilter{})
|
|
assert.Nil(t, err)
|
|
|
|
// Test SaveAttachment (Create)
|
|
attachment := map[string]interface{}{
|
|
"file_id": "test-file-123",
|
|
"uid": "user-123",
|
|
"manager": "local",
|
|
"content_type": "image/jpeg",
|
|
"name": "test-image.jpg",
|
|
"guest": false,
|
|
"public": true,
|
|
"gzip": false,
|
|
"bytes": 102400,
|
|
"scope": []string{"user", "admin"},
|
|
"status": "uploaded",
|
|
"progress": "100%",
|
|
"error": nil,
|
|
}
|
|
|
|
v, err := store.SaveAttachment(attachment)
|
|
assert.Nil(t, err)
|
|
fileID := v.(string)
|
|
assert.Equal(t, "test-file-123", fileID)
|
|
|
|
// Test GetAttachment
|
|
attachmentData, err := store.GetAttachment(fileID)
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, attachmentData)
|
|
assert.Equal(t, "test-file-123", attachmentData["file_id"])
|
|
assert.Equal(t, "user-123", attachmentData["uid"])
|
|
assert.Equal(t, "local", attachmentData["manager"])
|
|
assert.Equal(t, "image/jpeg", attachmentData["content_type"])
|
|
assert.Equal(t, "test-image.jpg", attachmentData["name"])
|
|
assert.Equal(t, int64(1), attachmentData["public"])
|
|
assert.Equal(t, []interface{}{"user", "admin"}, attachmentData["scope"])
|
|
assert.Equal(t, "uploaded", attachmentData["status"])
|
|
assert.Equal(t, "100%", attachmentData["progress"])
|
|
assert.Nil(t, attachmentData["error"])
|
|
|
|
// Test SaveAttachment (Update)
|
|
attachment["name"] = "updated-image.jpg"
|
|
attachment["bytes"] = 204800
|
|
attachment["status"] = "indexing"
|
|
attachment["progress"] = "Processing..."
|
|
attachment["error"] = "Connection timeout"
|
|
v, err = store.SaveAttachment(attachment)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "test-file-123", v.(string))
|
|
|
|
// Verify update
|
|
attachmentData, err = store.GetAttachment(fileID)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "updated-image.jpg", attachmentData["name"])
|
|
assert.Equal(t, int64(204800), attachmentData["bytes"])
|
|
assert.Equal(t, "indexing", attachmentData["status"])
|
|
assert.Equal(t, "Processing...", attachmentData["progress"])
|
|
assert.Equal(t, "Connection timeout", attachmentData["error"])
|
|
|
|
// Test GetAttachments with filters
|
|
resp, err := store.GetAttachments(AttachmentFilter{
|
|
UID: "user-123",
|
|
Manager: "local",
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 1, len(resp.Data))
|
|
assert.Equal(t, "test-file-123", resp.Data[0]["file_id"])
|
|
|
|
// Test with non-existent file
|
|
nonExistentData, err := store.GetAttachment("non-existent-file")
|
|
assert.Error(t, err)
|
|
assert.Nil(t, nonExistentData)
|
|
assert.Contains(t, err.Error(), "is empty")
|
|
|
|
// Test DeleteAttachment
|
|
err = store.DeleteAttachment(fileID)
|
|
assert.Nil(t, err)
|
|
|
|
// Verify deletion
|
|
_, err = store.GetAttachment(fileID)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestXunKnowledgeCRUD(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_knowledge")
|
|
|
|
// Drop knowledge table before test
|
|
err := capsule.Schema().DropTableIfExists("__unit_test_conversation_knowledge")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Add a small delay to ensure table is created
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "default",
|
|
Prefix: "__unit_test_conversation_",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Clean up any existing data
|
|
_, err = store.DeleteKnowledges(KnowledgeFilter{})
|
|
assert.Nil(t, err)
|
|
|
|
// Test SaveKnowledge (Create)
|
|
knowledge := map[string]interface{}{
|
|
"collection_id": "test-collection-123",
|
|
"name": "Test Knowledge Collection",
|
|
"description": "A test knowledge collection for unit tests",
|
|
"uid": "user-123",
|
|
"public": true,
|
|
"readonly": false,
|
|
"system": false,
|
|
"sort": 100,
|
|
"cover": "cover-image.jpg",
|
|
"scope": []string{"user", "admin"},
|
|
"option": map[string]interface{}{"embedding": "openai", "chunk_size": 1000},
|
|
}
|
|
|
|
v, err := store.SaveKnowledge(knowledge)
|
|
assert.Nil(t, err)
|
|
collectionID := v.(string)
|
|
assert.Equal(t, "test-collection-123", collectionID)
|
|
|
|
// Test GetKnowledge
|
|
knowledgeData, err := store.GetKnowledge(collectionID)
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, knowledgeData)
|
|
assert.Equal(t, "test-collection-123", knowledgeData["collection_id"])
|
|
assert.Equal(t, "Test Knowledge Collection", knowledgeData["name"])
|
|
assert.Equal(t, "A test knowledge collection for unit tests", knowledgeData["description"])
|
|
assert.Equal(t, "user-123", knowledgeData["uid"])
|
|
assert.Equal(t, int64(1), knowledgeData["public"])
|
|
assert.Equal(t, int64(100), knowledgeData["sort"])
|
|
assert.Equal(t, []interface{}{"user", "admin"}, knowledgeData["scope"])
|
|
assert.Equal(t, map[string]interface{}{"embedding": "openai", "chunk_size": float64(1000)}, knowledgeData["option"])
|
|
|
|
// Test SaveKnowledge (Update)
|
|
knowledge["name"] = "Updated Knowledge Collection"
|
|
knowledge["description"] = "Updated description"
|
|
knowledge["sort"] = 200
|
|
v, err = store.SaveKnowledge(knowledge)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "test-collection-123", v.(string))
|
|
|
|
// Verify update
|
|
knowledgeData, err = store.GetKnowledge(collectionID)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "Updated Knowledge Collection", knowledgeData["name"])
|
|
assert.Equal(t, "Updated description", knowledgeData["description"])
|
|
assert.Equal(t, int64(200), knowledgeData["sort"])
|
|
|
|
// Test knowledge without sort field (should get default value 9999)
|
|
knowledgeWithoutSort := map[string]interface{}{
|
|
"collection_id": "test-collection-456",
|
|
"name": "Test Knowledge Without Sort",
|
|
"description": "Test knowledge without explicit sort value",
|
|
"uid": "user-123",
|
|
}
|
|
v2, err := store.SaveKnowledge(knowledgeWithoutSort)
|
|
assert.Nil(t, err)
|
|
|
|
// Verify default sort value
|
|
knowledgeData2, err := store.GetKnowledge(v2.(string))
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, int64(9999), knowledgeData2["sort"])
|
|
|
|
// Test GetKnowledges with filters
|
|
resp, err := store.GetKnowledges(KnowledgeFilter{
|
|
UID: "user-123",
|
|
Keywords: "Updated",
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 1, len(resp.Data))
|
|
assert.Equal(t, "test-collection-123", resp.Data[0]["collection_id"])
|
|
|
|
// Test with non-existent collection
|
|
nonExistentData, err := store.GetKnowledge("non-existent-collection")
|
|
assert.Error(t, err)
|
|
assert.Nil(t, nonExistentData)
|
|
assert.Contains(t, err.Error(), "is empty")
|
|
|
|
// Test DeleteKnowledge
|
|
err = store.DeleteKnowledge(collectionID)
|
|
assert.Nil(t, err)
|
|
err = store.DeleteKnowledge(v2.(string))
|
|
assert.Nil(t, err)
|
|
|
|
// Verify deletion
|
|
_, err = store.GetKnowledge(collectionID)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestXunKnowledgeFiltering(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_knowledge")
|
|
|
|
// Drop knowledge table before test
|
|
err := capsule.Schema().DropTableIfExists("__unit_test_conversation_knowledge")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Add a small delay to ensure table is created
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "default",
|
|
Prefix: "__unit_test_conversation_",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create test data for filtering tests
|
|
testKnowledges := []map[string]interface{}{}
|
|
for i := 0; i < 15; i++ {
|
|
knowledge := map[string]interface{}{
|
|
"collection_id": fmt.Sprintf("test-collection-%d", i),
|
|
"name": fmt.Sprintf("Collection %d", i),
|
|
"description": fmt.Sprintf("Description for collection %d", i),
|
|
"uid": fmt.Sprintf("user-%d", i%3),
|
|
"public": i%2 == 0,
|
|
"readonly": i%3 == 0,
|
|
"system": i%4 == 0,
|
|
"sort": 100 + i*10, // Different sort values for testing ordering
|
|
"cover": fmt.Sprintf("cover%d.jpg", i),
|
|
}
|
|
id, err := store.SaveKnowledge(knowledge)
|
|
assert.Nil(t, err)
|
|
knowledge["collection_id"] = id
|
|
testKnowledges = append(testKnowledges, knowledge)
|
|
}
|
|
|
|
// Test sorting functionality - should return results ordered by sort ASC then created_at DESC
|
|
respAll, err := store.GetKnowledges(KnowledgeFilter{
|
|
Page: 1,
|
|
PageSize: 15,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 15, len(respAll.Data))
|
|
|
|
// Verify sort order - first item should have the smallest sort value
|
|
firstSort := respAll.Data[0]["sort"].(int64)
|
|
lastSort := respAll.Data[len(respAll.Data)-1]["sort"].(int64)
|
|
assert.LessOrEqual(t, firstSort, lastSort, "Results should be ordered by sort ASC")
|
|
|
|
// More specific sort order verification
|
|
for i := 1; i < len(respAll.Data); i++ {
|
|
prevSort := respAll.Data[i-1]["sort"].(int64)
|
|
currSort := respAll.Data[i]["sort"].(int64)
|
|
assert.LessOrEqual(t, prevSort, currSort, "Sort order should be ascending")
|
|
}
|
|
|
|
// Test filtering by UID
|
|
resp, err := store.GetKnowledges(KnowledgeFilter{
|
|
UID: "user-0",
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, len(resp.Data), 0)
|
|
|
|
// Test filtering by public status
|
|
publicTrue := true
|
|
resp, err = store.GetKnowledges(KnowledgeFilter{
|
|
Public: &publicTrue,
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, len(resp.Data), 0)
|
|
|
|
// Test filtering by readonly status
|
|
readonlyTrue := true
|
|
resp, err = store.GetKnowledges(KnowledgeFilter{
|
|
Readonly: &readonlyTrue,
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, len(resp.Data), 0)
|
|
|
|
// Test filtering by system status
|
|
systemTrue := true
|
|
resp, err = store.GetKnowledges(KnowledgeFilter{
|
|
System: &systemTrue,
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, len(resp.Data), 0)
|
|
|
|
// Test filtering by keywords
|
|
resp, err = store.GetKnowledges(KnowledgeFilter{
|
|
Keywords: "Collection 1",
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, len(resp.Data), 0)
|
|
|
|
// Test DeleteKnowledges with filter
|
|
count, err := store.DeleteKnowledges(KnowledgeFilter{
|
|
UID: "user-0",
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, count, int64(0))
|
|
|
|
// Verify deletion
|
|
resp, err = store.GetKnowledges(KnowledgeFilter{
|
|
UID: "user-0",
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, len(resp.Data))
|
|
|
|
// Clean up all test data
|
|
_, err = store.DeleteKnowledges(KnowledgeFilter{})
|
|
assert.Nil(t, err)
|
|
}
|
|
|
|
func TestXunAttachmentFiltering(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_attachment")
|
|
|
|
// Drop attachment table before test
|
|
err := capsule.Schema().DropTableIfExists("__unit_test_conversation_attachment")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Add a small delay to ensure table is created
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "default",
|
|
Prefix: "__unit_test_conversation_",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create test data for filtering tests
|
|
testAttachments := []map[string]interface{}{}
|
|
for i := 0; i < 15; i++ {
|
|
attachment := map[string]interface{}{
|
|
"file_id": fmt.Sprintf("test-file-%d", i),
|
|
"uid": fmt.Sprintf("user-%d", i%3),
|
|
"manager": fmt.Sprintf("manager%d", i%2),
|
|
"content_type": fmt.Sprintf("type/%d", i%4),
|
|
"name": fmt.Sprintf("file%d.txt", i),
|
|
"guest": i%2 == 0,
|
|
"public": i%3 == 0,
|
|
"gzip": i%4 == 0,
|
|
"bytes": 1024 * (i + 1),
|
|
"collection_id": fmt.Sprintf("collection-%d", i%5),
|
|
}
|
|
id, err := store.SaveAttachment(attachment)
|
|
assert.Nil(t, err)
|
|
attachment["file_id"] = id
|
|
testAttachments = append(testAttachments, attachment)
|
|
}
|
|
|
|
// Test filtering by UID
|
|
resp, err := store.GetAttachments(AttachmentFilter{
|
|
UID: "user-0",
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, len(resp.Data), 0)
|
|
|
|
// Test filtering by manager
|
|
resp, err = store.GetAttachments(AttachmentFilter{
|
|
Manager: "manager0",
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, len(resp.Data), 0)
|
|
|
|
// Test filtering by content_type
|
|
resp, err = store.GetAttachments(AttachmentFilter{
|
|
ContentType: "type/0",
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, len(resp.Data), 0)
|
|
|
|
// Test filtering by guest status
|
|
guestTrue := true
|
|
resp, err = store.GetAttachments(AttachmentFilter{
|
|
Guest: &guestTrue,
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, len(resp.Data), 0)
|
|
|
|
// Test filtering by public status
|
|
publicTrue := true
|
|
resp, err = store.GetAttachments(AttachmentFilter{
|
|
Public: &publicTrue,
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, len(resp.Data), 0)
|
|
|
|
// Test filtering by keywords
|
|
resp, err = store.GetAttachments(AttachmentFilter{
|
|
Keywords: "file1",
|
|
Page: 1,
|
|
PageSize: 10,
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, len(resp.Data), 0)
|
|
|
|
// Test DeleteAttachments with filter
|
|
count, err := store.DeleteAttachments(AttachmentFilter{
|
|
Manager: "manager0",
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Greater(t, count, int64(0))
|
|
|
|
// Verify deletion
|
|
resp, err = store.GetAttachments(AttachmentFilter{
|
|
Manager: "manager0",
|
|
})
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 0, len(resp.Data))
|
|
|
|
// Clean up all test data
|
|
_, err = store.DeleteAttachments(AttachmentFilter{})
|
|
assert.Nil(t, err)
|
|
}
|
|
|
|
func TestXunAttachmentStatusFields(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_attachment")
|
|
|
|
// Drop attachment table before test
|
|
err := capsule.Schema().DropTableIfExists("__unit_test_conversation_attachment")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Add a small delay to ensure table is created
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
store, err := NewXun(Setting{
|
|
Connector: "default",
|
|
Prefix: "__unit_test_conversation_",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Clean up any existing data
|
|
_, err = store.DeleteAttachments(AttachmentFilter{})
|
|
assert.Nil(t, err)
|
|
|
|
// Test all possible enum status values
|
|
statusValues := []string{"uploading", "uploaded", "indexing", "indexed", "upload_failed", "index_failed"}
|
|
|
|
for i, status := range statusValues {
|
|
// Create attachment with specific status
|
|
attachment := map[string]interface{}{
|
|
"file_id": fmt.Sprintf("test-file-%s-%d", status, i),
|
|
"uid": "user-123",
|
|
"manager": "local",
|
|
"content_type": "image/jpeg",
|
|
"name": fmt.Sprintf("test-%s.jpg", status),
|
|
"guest": false,
|
|
"public": true,
|
|
"gzip": false,
|
|
"bytes": 102400,
|
|
"status": status,
|
|
"progress": fmt.Sprintf("%s in progress", status),
|
|
"error": nil,
|
|
}
|
|
|
|
// Set error message for failed statuses
|
|
if status == "upload_failed" || status == "index_failed" {
|
|
attachment["error"] = fmt.Sprintf("%s error occurred", status)
|
|
}
|
|
|
|
v, err := store.SaveAttachment(attachment)
|
|
assert.Nil(t, err)
|
|
fileID := v.(string)
|
|
|
|
// Verify the attachment was saved with correct status
|
|
attachmentData, err := store.GetAttachment(fileID)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, status, attachmentData["status"])
|
|
assert.Equal(t, fmt.Sprintf("%s in progress", status), attachmentData["progress"])
|
|
|
|
if status == "upload_failed" || status == "index_failed" {
|
|
assert.Equal(t, fmt.Sprintf("%s error occurred", status), attachmentData["error"])
|
|
} else {
|
|
assert.Nil(t, attachmentData["error"])
|
|
}
|
|
}
|
|
|
|
// Test default status value (should be "uploading")
|
|
attachmentWithoutStatus := map[string]interface{}{
|
|
"file_id": "test-file-default",
|
|
"uid": "user-123",
|
|
"manager": "local",
|
|
"content_type": "image/jpeg",
|
|
"name": "test-default.jpg",
|
|
"guest": false,
|
|
"public": true,
|
|
"gzip": false,
|
|
"bytes": 102400,
|
|
// status not specified - should use default
|
|
}
|
|
|
|
v, err := store.SaveAttachment(attachmentWithoutStatus)
|
|
assert.Nil(t, err)
|
|
fileID := v.(string)
|
|
|
|
// Verify default status
|
|
attachmentData, err := store.GetAttachment(fileID)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "uploading", attachmentData["status"]) // Should be default value
|
|
assert.Nil(t, attachmentData["progress"]) // Should be null
|
|
assert.Nil(t, attachmentData["error"]) // Should be null
|
|
|
|
// Test updating status workflow: uploading -> uploaded -> indexing -> indexed
|
|
workflowAttachment := map[string]interface{}{
|
|
"file_id": "test-file-workflow",
|
|
"uid": "user-123",
|
|
"manager": "local",
|
|
"content_type": "text/plain",
|
|
"name": "workflow-test.txt",
|
|
"status": "uploading",
|
|
"progress": "Starting upload...",
|
|
}
|
|
|
|
v, err = store.SaveAttachment(workflowAttachment)
|
|
assert.Nil(t, err)
|
|
workflowFileID := v.(string)
|
|
|
|
// Update to uploaded
|
|
workflowAttachment["status"] = "uploaded"
|
|
workflowAttachment["progress"] = "Upload completed, starting indexing..."
|
|
_, err = store.SaveAttachment(workflowAttachment)
|
|
assert.Nil(t, err)
|
|
|
|
attachmentData, err = store.GetAttachment(workflowFileID)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "uploaded", attachmentData["status"])
|
|
assert.Equal(t, "Upload completed, starting indexing...", attachmentData["progress"])
|
|
|
|
// Update to indexing
|
|
workflowAttachment["status"] = "indexing"
|
|
workflowAttachment["progress"] = "Indexing in progress..."
|
|
_, err = store.SaveAttachment(workflowAttachment)
|
|
assert.Nil(t, err)
|
|
|
|
attachmentData, err = store.GetAttachment(workflowFileID)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "indexing", attachmentData["status"])
|
|
assert.Equal(t, "Indexing in progress...", attachmentData["progress"])
|
|
|
|
// Update to indexed (final state)
|
|
workflowAttachment["status"] = "indexed"
|
|
workflowAttachment["progress"] = "Indexing completed"
|
|
_, err = store.SaveAttachment(workflowAttachment)
|
|
assert.Nil(t, err)
|
|
|
|
attachmentData, err = store.GetAttachment(workflowFileID)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "indexed", attachmentData["status"])
|
|
assert.Equal(t, "Indexing completed", attachmentData["progress"])
|
|
|
|
// Clean up test data
|
|
_, err = store.DeleteAttachments(AttachmentFilter{})
|
|
assert.Nil(t, err)
|
|
}
|