- Introduced `GetInfo` method in the Assistant struct to return basic assistant information with optional locale support for internationalization. - Implemented `GetInfoByIDs` function to retrieve information for multiple assistants based on their IDs, enhancing batch processing capabilities. - Updated chat buffer management to include a mode parameter, allowing for dynamic switching between chat and task modes. - Enhanced message handling to store and retrieve the mode associated with each message, improving context tracking during chat sessions. - Revised tests to validate the new functionalities, ensuring accurate retrieval of assistant information and proper mode management in chat operations.
1180 lines
29 KiB
Go
1180 lines
29 KiB
Go
package xun_test
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
goumodel "github.com/yaoapp/gou/model"
|
|
"github.com/yaoapp/xun/dbal/query"
|
|
"github.com/yaoapp/yao/agent/store/types"
|
|
"github.com/yaoapp/yao/agent/store/xun"
|
|
"github.com/yaoapp/yao/config"
|
|
"github.com/yaoapp/yao/test"
|
|
)
|
|
|
|
// TestCreateChat tests creating chat sessions
|
|
func TestCreateChat(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
store, err := xun.NewXun(types.Setting{
|
|
Connector: "default",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create store: %v", err)
|
|
}
|
|
|
|
t.Run("CreateNewChat", func(t *testing.T) {
|
|
chat := &types.Chat{
|
|
AssistantID: "test_assistant",
|
|
Title: "Test Chat",
|
|
Status: "active",
|
|
Share: "private",
|
|
}
|
|
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
|
|
if chat.ChatID == "" {
|
|
t.Error("Expected chat_id to be generated")
|
|
}
|
|
|
|
t.Logf("Created chat with ID: %s", chat.ChatID)
|
|
|
|
// Clean up
|
|
_ = store.DeleteChat(chat.ChatID)
|
|
})
|
|
|
|
t.Run("CreateChatWithAllFields", func(t *testing.T) {
|
|
now := time.Now()
|
|
chat := &types.Chat{
|
|
AssistantID: "test_assistant",
|
|
LastConnector: "openai",
|
|
Title: "Full Chat",
|
|
LastMode: "task",
|
|
Status: "active",
|
|
Public: true,
|
|
Share: "team",
|
|
Sort: 100,
|
|
LastMessageAt: &now,
|
|
Metadata: map[string]interface{}{
|
|
"source": "test",
|
|
"tags": []string{"test", "chat"},
|
|
},
|
|
}
|
|
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
|
|
// Retrieve and verify
|
|
retrieved, err := store.GetChat(chat.ChatID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to retrieve chat: %v", err)
|
|
}
|
|
|
|
if retrieved.Title != "Full Chat" {
|
|
t.Errorf("Expected title 'Full Chat', got '%s'", retrieved.Title)
|
|
}
|
|
if retrieved.LastConnector != "openai" {
|
|
t.Errorf("Expected last_connector 'openai', got '%s'", retrieved.LastConnector)
|
|
}
|
|
if retrieved.LastMode != "task" {
|
|
t.Errorf("Expected last_mode 'task', got '%s'", retrieved.LastMode)
|
|
}
|
|
if !retrieved.Public {
|
|
t.Error("Expected public to be true")
|
|
}
|
|
if retrieved.Share != "team" {
|
|
t.Errorf("Expected share 'team', got '%s'", retrieved.Share)
|
|
}
|
|
if retrieved.Sort != 100 {
|
|
t.Errorf("Expected sort 100, got %d", retrieved.Sort)
|
|
}
|
|
if retrieved.Metadata == nil {
|
|
t.Error("Expected metadata to be set")
|
|
}
|
|
|
|
// Clean up
|
|
_ = store.DeleteChat(chat.ChatID)
|
|
})
|
|
|
|
t.Run("CreateChatWithCustomID", func(t *testing.T) {
|
|
customID := fmt.Sprintf("custom_chat_%d", time.Now().UnixNano())
|
|
chat := &types.Chat{
|
|
ChatID: customID,
|
|
AssistantID: "test_assistant",
|
|
}
|
|
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
|
|
if chat.ChatID != customID {
|
|
t.Errorf("Expected chat_id '%s', got '%s'", customID, chat.ChatID)
|
|
}
|
|
|
|
// Clean up
|
|
_ = store.DeleteChat(chat.ChatID)
|
|
})
|
|
|
|
t.Run("CreateDuplicateChatFails", func(t *testing.T) {
|
|
chat := &types.Chat{
|
|
AssistantID: "test_assistant",
|
|
}
|
|
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create first chat: %v", err)
|
|
}
|
|
|
|
// Try to create with same ID
|
|
duplicateChat := &types.Chat{
|
|
ChatID: chat.ChatID,
|
|
AssistantID: "test_assistant",
|
|
}
|
|
|
|
err = store.CreateChat(duplicateChat)
|
|
if err == nil {
|
|
t.Error("Expected error when creating duplicate chat")
|
|
}
|
|
|
|
// Clean up
|
|
_ = store.DeleteChat(chat.ChatID)
|
|
})
|
|
|
|
t.Run("CreateChatWithoutAssistantIDFails", func(t *testing.T) {
|
|
chat := &types.Chat{
|
|
Title: "No Assistant",
|
|
}
|
|
|
|
err := store.CreateChat(chat)
|
|
if err == nil {
|
|
t.Error("Expected error when creating chat without assistant_id")
|
|
}
|
|
})
|
|
|
|
t.Run("CreateNilChatFails", func(t *testing.T) {
|
|
err := store.CreateChat(nil)
|
|
if err == nil {
|
|
t.Error("Expected error when creating nil chat")
|
|
}
|
|
})
|
|
|
|
t.Run("CreateChatWithDefaults", func(t *testing.T) {
|
|
chat := &types.Chat{
|
|
AssistantID: "test_assistant",
|
|
}
|
|
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
|
|
// Retrieve and verify defaults
|
|
retrieved, err := store.GetChat(chat.ChatID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to retrieve chat: %v", err)
|
|
}
|
|
|
|
// last_mode is nullable, so it should be empty by default
|
|
if retrieved.LastMode != "" {
|
|
t.Errorf("Expected default last_mode to be empty, got '%s'", retrieved.LastMode)
|
|
}
|
|
if retrieved.Status != "active" {
|
|
t.Errorf("Expected default status 'active', got '%s'", retrieved.Status)
|
|
}
|
|
if retrieved.Share != "private" {
|
|
t.Errorf("Expected default share 'private', got '%s'", retrieved.Share)
|
|
}
|
|
|
|
// Clean up
|
|
_ = store.DeleteChat(chat.ChatID)
|
|
})
|
|
}
|
|
|
|
// TestGetChat tests retrieving chat sessions
|
|
func TestGetChat(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
store, err := xun.NewXun(types.Setting{
|
|
Connector: "default",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create store: %v", err)
|
|
}
|
|
|
|
t.Run("GetExistingChat", func(t *testing.T) {
|
|
// Create chat first
|
|
chat := &types.Chat{
|
|
AssistantID: "test_assistant",
|
|
Title: "Get Test Chat",
|
|
}
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
|
|
// Get it
|
|
retrieved, err := store.GetChat(chat.ChatID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get chat: %v", err)
|
|
}
|
|
|
|
if retrieved.ChatID != chat.ChatID {
|
|
t.Errorf("Expected chat_id '%s', got '%s'", chat.ChatID, retrieved.ChatID)
|
|
}
|
|
if retrieved.Title != "Get Test Chat" {
|
|
t.Errorf("Expected title 'Get Test Chat', got '%s'", retrieved.Title)
|
|
}
|
|
|
|
// Clean up
|
|
_ = store.DeleteChat(chat.ChatID)
|
|
})
|
|
|
|
t.Run("GetNonExistentChat", func(t *testing.T) {
|
|
_, err := store.GetChat("nonexistent_chat_id")
|
|
if err == nil {
|
|
t.Error("Expected error when getting non-existent chat")
|
|
}
|
|
})
|
|
|
|
t.Run("GetChatWithEmptyID", func(t *testing.T) {
|
|
_, err := store.GetChat("")
|
|
if err == nil {
|
|
t.Error("Expected error when getting chat with empty ID")
|
|
}
|
|
})
|
|
|
|
t.Run("GetDeletedChatFails", func(t *testing.T) {
|
|
// Create and delete chat
|
|
chat := &types.Chat{
|
|
AssistantID: "test_assistant",
|
|
}
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
|
|
err = store.DeleteChat(chat.ChatID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to delete chat: %v", err)
|
|
}
|
|
|
|
// Try to get deleted chat
|
|
_, err = store.GetChat(chat.ChatID)
|
|
if err == nil {
|
|
t.Error("Expected error when getting deleted chat")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestUpdateChat tests updating chat sessions
|
|
func TestUpdateChat(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
store, err := xun.NewXun(types.Setting{
|
|
Connector: "default",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create store: %v", err)
|
|
}
|
|
|
|
t.Run("UpdateTitle", func(t *testing.T) {
|
|
chat := &types.Chat{
|
|
AssistantID: "test_assistant",
|
|
Title: "Original Title",
|
|
}
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
|
|
err = store.UpdateChat(chat.ChatID, map[string]interface{}{
|
|
"title": "Updated Title",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to update chat: %v", err)
|
|
}
|
|
|
|
retrieved, err := store.GetChat(chat.ChatID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to retrieve chat: %v", err)
|
|
}
|
|
|
|
if retrieved.Title != "Updated Title" {
|
|
t.Errorf("Expected title 'Updated Title', got '%s'", retrieved.Title)
|
|
}
|
|
|
|
// Clean up
|
|
_ = store.DeleteChat(chat.ChatID)
|
|
})
|
|
|
|
t.Run("UpdateLastConnector", func(t *testing.T) {
|
|
chat := &types.Chat{
|
|
AssistantID: "test_assistant",
|
|
LastConnector: "openai",
|
|
}
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
|
|
// Verify initial connector
|
|
retrieved, err := store.GetChat(chat.ChatID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to retrieve chat: %v", err)
|
|
}
|
|
if retrieved.LastConnector != "openai" {
|
|
t.Errorf("Expected last_connector 'openai', got '%s'", retrieved.LastConnector)
|
|
}
|
|
|
|
// Update to different connector (simulating user switching connector)
|
|
err = store.UpdateChat(chat.ChatID, map[string]interface{}{
|
|
"last_connector": "anthropic",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to update chat: %v", err)
|
|
}
|
|
|
|
// Verify updated connector
|
|
retrieved, err = store.GetChat(chat.ChatID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to retrieve chat: %v", err)
|
|
}
|
|
if retrieved.LastConnector != "anthropic" {
|
|
t.Errorf("Expected last_connector 'anthropic', got '%s'", retrieved.LastConnector)
|
|
}
|
|
|
|
// Clean up
|
|
_ = store.DeleteChat(chat.ChatID)
|
|
})
|
|
|
|
t.Run("UpdateLastConnectorAndLastMessageAt", func(t *testing.T) {
|
|
// This simulates what FlushBuffer does
|
|
chat := &types.Chat{
|
|
AssistantID: "test_assistant",
|
|
LastConnector: "openai",
|
|
}
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
|
|
// Update both fields together (like FlushBuffer does)
|
|
now := time.Now()
|
|
err = store.UpdateChat(chat.ChatID, map[string]interface{}{
|
|
"last_message_at": now,
|
|
"last_connector": "claude",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to update chat: %v", err)
|
|
}
|
|
|
|
retrieved, err := store.GetChat(chat.ChatID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to retrieve chat: %v", err)
|
|
}
|
|
|
|
if retrieved.LastConnector != "claude" {
|
|
t.Errorf("Expected last_connector 'claude', got '%s'", retrieved.LastConnector)
|
|
}
|
|
if retrieved.LastMessageAt == nil {
|
|
t.Error("Expected last_message_at to be set")
|
|
}
|
|
|
|
// Clean up
|
|
_ = store.DeleteChat(chat.ChatID)
|
|
})
|
|
|
|
t.Run("UpdateMultipleFields", func(t *testing.T) {
|
|
chat := &types.Chat{
|
|
AssistantID: "test_assistant",
|
|
Title: "Original",
|
|
Status: "active",
|
|
Share: "private",
|
|
}
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
|
|
err = store.UpdateChat(chat.ChatID, map[string]interface{}{
|
|
"title": "Updated",
|
|
"status": "archived",
|
|
"share": "team",
|
|
"public": true,
|
|
"sort": 50,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to update chat: %v", err)
|
|
}
|
|
|
|
retrieved, err := store.GetChat(chat.ChatID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to retrieve chat: %v", err)
|
|
}
|
|
|
|
if retrieved.Title != "Updated" {
|
|
t.Errorf("Expected title 'Updated', got '%s'", retrieved.Title)
|
|
}
|
|
if retrieved.Status != "archived" {
|
|
t.Errorf("Expected status 'archived', got '%s'", retrieved.Status)
|
|
}
|
|
if retrieved.Share != "team" {
|
|
t.Errorf("Expected share 'team', got '%s'", retrieved.Share)
|
|
}
|
|
if !retrieved.Public {
|
|
t.Error("Expected public to be true")
|
|
}
|
|
if retrieved.Sort != 50 {
|
|
t.Errorf("Expected sort 50, got %d", retrieved.Sort)
|
|
}
|
|
|
|
// Clean up
|
|
_ = store.DeleteChat(chat.ChatID)
|
|
})
|
|
|
|
t.Run("UpdateMetadata", func(t *testing.T) {
|
|
chat := &types.Chat{
|
|
AssistantID: "test_assistant",
|
|
}
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
|
|
err = store.UpdateChat(chat.ChatID, map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"key1": "value1",
|
|
"key2": 123,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to update metadata: %v", err)
|
|
}
|
|
|
|
retrieved, err := store.GetChat(chat.ChatID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to retrieve chat: %v", err)
|
|
}
|
|
|
|
if retrieved.Metadata == nil {
|
|
t.Fatal("Expected metadata to be set")
|
|
}
|
|
if retrieved.Metadata["key1"] != "value1" {
|
|
t.Errorf("Expected metadata key1 'value1', got '%v'", retrieved.Metadata["key1"])
|
|
}
|
|
|
|
// Clean up
|
|
_ = store.DeleteChat(chat.ChatID)
|
|
})
|
|
|
|
t.Run("UpdateNonExistentChatFails", func(t *testing.T) {
|
|
err := store.UpdateChat("nonexistent_chat", map[string]interface{}{
|
|
"title": "Test",
|
|
})
|
|
if err == nil {
|
|
t.Error("Expected error when updating non-existent chat")
|
|
}
|
|
})
|
|
|
|
t.Run("UpdateWithEmptyIDFails", func(t *testing.T) {
|
|
err := store.UpdateChat("", map[string]interface{}{
|
|
"title": "Test",
|
|
})
|
|
if err == nil {
|
|
t.Error("Expected error when updating with empty ID")
|
|
}
|
|
})
|
|
|
|
t.Run("UpdateWithEmptyFieldsFails", func(t *testing.T) {
|
|
chat := &types.Chat{
|
|
AssistantID: "test_assistant",
|
|
}
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
|
|
err = store.UpdateChat(chat.ChatID, map[string]interface{}{})
|
|
if err == nil {
|
|
t.Error("Expected error when updating with empty fields")
|
|
}
|
|
|
|
// Clean up
|
|
_ = store.DeleteChat(chat.ChatID)
|
|
})
|
|
|
|
t.Run("UpdateSkipsSystemFields", func(t *testing.T) {
|
|
chat := &types.Chat{
|
|
AssistantID: "test_assistant",
|
|
}
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
|
|
originalID := chat.ChatID
|
|
|
|
// Try to update system fields
|
|
err = store.UpdateChat(chat.ChatID, map[string]interface{}{
|
|
"chat_id": "new_id",
|
|
"title": "Valid Update",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to update chat: %v", err)
|
|
}
|
|
|
|
// Verify chat_id unchanged
|
|
retrieved, err := store.GetChat(originalID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to retrieve chat: %v", err)
|
|
}
|
|
|
|
if retrieved.ChatID != originalID {
|
|
t.Errorf("Expected chat_id to remain '%s', got '%s'", originalID, retrieved.ChatID)
|
|
}
|
|
if retrieved.Title != "Valid Update" {
|
|
t.Errorf("Expected title 'Valid Update', got '%s'", retrieved.Title)
|
|
}
|
|
|
|
// Clean up
|
|
_ = store.DeleteChat(chat.ChatID)
|
|
})
|
|
}
|
|
|
|
// TestDeleteChat tests deleting chat sessions
|
|
func TestDeleteChat(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
store, err := xun.NewXun(types.Setting{
|
|
Connector: "default",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create store: %v", err)
|
|
}
|
|
|
|
t.Run("DeleteExistingChat", func(t *testing.T) {
|
|
chat := &types.Chat{
|
|
AssistantID: "test_assistant",
|
|
}
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
|
|
err = store.DeleteChat(chat.ChatID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to delete chat: %v", err)
|
|
}
|
|
|
|
// Verify deleted
|
|
_, err = store.GetChat(chat.ChatID)
|
|
if err == nil {
|
|
t.Error("Expected error when getting deleted chat")
|
|
}
|
|
})
|
|
|
|
t.Run("DeleteNonExistentChatFails", func(t *testing.T) {
|
|
err := store.DeleteChat("nonexistent_chat")
|
|
if err == nil {
|
|
t.Error("Expected error when deleting non-existent chat")
|
|
}
|
|
})
|
|
|
|
t.Run("DeleteWithEmptyIDFails", func(t *testing.T) {
|
|
err := store.DeleteChat("")
|
|
if err == nil {
|
|
t.Error("Expected error when deleting with empty ID")
|
|
}
|
|
})
|
|
|
|
t.Run("DeleteAlreadyDeletedChatFails", func(t *testing.T) {
|
|
chat := &types.Chat{
|
|
AssistantID: "test_assistant",
|
|
}
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
|
|
// Delete first time
|
|
err = store.DeleteChat(chat.ChatID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to delete chat: %v", err)
|
|
}
|
|
|
|
// Try to delete again
|
|
err = store.DeleteChat(chat.ChatID)
|
|
if err == nil {
|
|
t.Error("Expected error when deleting already deleted chat")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestListChats tests listing chat sessions
|
|
func TestListChats(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
store, err := xun.NewXun(types.Setting{
|
|
Connector: "default",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create store: %v", err)
|
|
}
|
|
|
|
// Create test chats
|
|
chatIDs := []string{}
|
|
for i := 0; i < 5; i++ {
|
|
chat := &types.Chat{
|
|
AssistantID: "test_assistant",
|
|
Title: fmt.Sprintf("Chat %d", i),
|
|
Status: "active",
|
|
}
|
|
if i >= 3 {
|
|
chat.Status = "archived"
|
|
}
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
chatIDs = append(chatIDs, chat.ChatID)
|
|
|
|
// Add small delay to ensure different timestamps
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
// Clean up at the end
|
|
defer func() {
|
|
for _, id := range chatIDs {
|
|
_ = store.DeleteChat(id)
|
|
}
|
|
}()
|
|
|
|
t.Run("ListAllChats", func(t *testing.T) {
|
|
result, err := store.ListChats(types.ChatFilter{
|
|
Page: 1,
|
|
PageSize: 20,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats: %v", err)
|
|
}
|
|
|
|
if len(result.Data) < 5 {
|
|
t.Errorf("Expected at least 5 chats, got %d", len(result.Data))
|
|
}
|
|
})
|
|
|
|
t.Run("ListChatsByStatus", func(t *testing.T) {
|
|
result, err := store.ListChats(types.ChatFilter{
|
|
Status: "active",
|
|
Page: 1,
|
|
PageSize: 20,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats: %v", err)
|
|
}
|
|
|
|
for _, chat := range result.Data {
|
|
if chat.Status != "active" {
|
|
t.Errorf("Expected status 'active', got '%s'", chat.Status)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("ListChatsByAssistant", func(t *testing.T) {
|
|
result, err := store.ListChats(types.ChatFilter{
|
|
AssistantID: "test_assistant",
|
|
Page: 1,
|
|
PageSize: 20,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats: %v", err)
|
|
}
|
|
|
|
for _, chat := range result.Data {
|
|
if chat.AssistantID != "test_assistant" {
|
|
t.Errorf("Expected assistant_id 'test_assistant', got '%s'", chat.AssistantID)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("ListChatsByKeywords", func(t *testing.T) {
|
|
result, err := store.ListChats(types.ChatFilter{
|
|
Keywords: "Chat 1",
|
|
Page: 1,
|
|
PageSize: 20,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats: %v", err)
|
|
}
|
|
|
|
found := false
|
|
for _, chat := range result.Data {
|
|
if chat.Title == "Chat 1" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("Expected to find chat with title 'Chat 1'")
|
|
}
|
|
})
|
|
|
|
t.Run("ListChatsPagination", func(t *testing.T) {
|
|
// First page
|
|
result1, err := store.ListChats(types.ChatFilter{
|
|
Page: 1,
|
|
PageSize: 2,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list first page: %v", err)
|
|
}
|
|
|
|
if len(result1.Data) > 2 {
|
|
t.Errorf("Expected max 2 chats, got %d", len(result1.Data))
|
|
}
|
|
if result1.Page != 1 {
|
|
t.Errorf("Expected page 1, got %d", result1.Page)
|
|
}
|
|
if result1.PageSize != 2 {
|
|
t.Errorf("Expected pagesize 2, got %d", result1.PageSize)
|
|
}
|
|
|
|
// Second page
|
|
if result1.Total > 2 {
|
|
result2, err := store.ListChats(types.ChatFilter{
|
|
Page: 2,
|
|
PageSize: 2,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list second page: %v", err)
|
|
}
|
|
if result2.Page != 2 {
|
|
t.Errorf("Expected page 2, got %d", result2.Page)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("ListChatsWithGrouping", func(t *testing.T) {
|
|
result, err := store.ListChats(types.ChatFilter{
|
|
GroupBy: "time",
|
|
Page: 1,
|
|
PageSize: 20,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats with grouping: %v", err)
|
|
}
|
|
|
|
// Should have groups when GroupBy is "time"
|
|
if result.Groups == nil {
|
|
t.Error("Expected groups to be set when GroupBy='time'")
|
|
}
|
|
|
|
// Verify group structure
|
|
for _, group := range result.Groups {
|
|
if group.Key == "" {
|
|
t.Error("Expected group key to be set")
|
|
}
|
|
if group.Label == "" {
|
|
t.Error("Expected group label to be set")
|
|
}
|
|
if group.Count != len(group.Chats) {
|
|
t.Errorf("Expected count %d to match chats length %d", group.Count, len(group.Chats))
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("ListChatsWithTimeRange", func(t *testing.T) {
|
|
now := time.Now()
|
|
yesterday := now.AddDate(0, 0, -1)
|
|
|
|
result, err := store.ListChats(types.ChatFilter{
|
|
StartTime: &yesterday,
|
|
EndTime: &now,
|
|
TimeField: "created_at",
|
|
Page: 1,
|
|
PageSize: 20,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats with time range: %v", err)
|
|
}
|
|
|
|
// Should return chats created within the time range
|
|
t.Logf("Found %d chats in time range", len(result.Data))
|
|
})
|
|
|
|
t.Run("ListChatsWithSorting", func(t *testing.T) {
|
|
// Ascending order
|
|
resultAsc, err := store.ListChats(types.ChatFilter{
|
|
OrderBy: "created_at",
|
|
Order: "asc",
|
|
Page: 1,
|
|
PageSize: 20,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats ascending: %v", err)
|
|
}
|
|
|
|
// Descending order
|
|
resultDesc, err := store.ListChats(types.ChatFilter{
|
|
OrderBy: "created_at",
|
|
Order: "desc",
|
|
Page: 1,
|
|
PageSize: 20,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats descending: %v", err)
|
|
}
|
|
|
|
// Verify different order
|
|
if len(resultAsc.Data) > 1 && len(resultDesc.Data) > 1 {
|
|
if resultAsc.Data[0].ChatID == resultDesc.Data[0].ChatID {
|
|
// This is fine if there's only one chat, but otherwise order should differ
|
|
if len(resultAsc.Data) > 1 {
|
|
t.Logf("First chat in asc: %s, first in desc: %s", resultAsc.Data[0].ChatID, resultDesc.Data[0].ChatID)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("ListChatsWithQueryFilter", func(t *testing.T) {
|
|
result, err := store.ListChats(types.ChatFilter{
|
|
Page: 1,
|
|
PageSize: 20,
|
|
QueryFilter: func(qb query.Query) {
|
|
qb.Where("status", "active")
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats with query filter: %v", err)
|
|
}
|
|
|
|
for _, chat := range result.Data {
|
|
if chat.Status != "active" {
|
|
t.Errorf("Expected status 'active', got '%s'", chat.Status)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestListChatsByUserAndTeam tests filtering chats by UserID and TeamID
|
|
func TestListChatsByUserAndTeam(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
store, err := xun.NewXun(types.Setting{
|
|
Connector: "default",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create store: %v", err)
|
|
}
|
|
|
|
// Create chats with different user/team combinations
|
|
// Note: __yao_created_by and __yao_team_id are managed by Yao's permission system
|
|
// For testing, we'll create chats and then update these fields directly via raw query
|
|
|
|
chat1 := &types.Chat{AssistantID: "test_assistant", Title: "User1 Team1 Chat"}
|
|
chat2 := &types.Chat{AssistantID: "test_assistant", Title: "User1 Team2 Chat"}
|
|
chat3 := &types.Chat{AssistantID: "test_assistant", Title: "User2 Team1 Chat"}
|
|
chat4 := &types.Chat{AssistantID: "test_assistant", Title: "User2 Team2 Chat"}
|
|
|
|
for _, chat := range []*types.Chat{chat1, chat2, chat3, chat4} {
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
}
|
|
defer func() {
|
|
store.DeleteChat(chat1.ChatID)
|
|
store.DeleteChat(chat2.ChatID)
|
|
store.DeleteChat(chat3.ChatID)
|
|
store.DeleteChat(chat4.ChatID)
|
|
}()
|
|
|
|
// Update permission fields directly for testing
|
|
// In production, these would be set by Yao's permission middleware
|
|
updatePermissionFields := func(chatID, userID, teamID string) error {
|
|
// Use Yao model to update permission fields
|
|
m := goumodel.Select("__yao.agent.chat")
|
|
if m == nil {
|
|
return fmt.Errorf("model __yao.agent.chat not found")
|
|
}
|
|
_, err := m.UpdateWhere(
|
|
goumodel.QueryParam{Wheres: []goumodel.QueryWhere{{Column: "chat_id", Value: chatID}}},
|
|
map[string]interface{}{
|
|
"__yao_created_by": userID,
|
|
"__yao_team_id": teamID,
|
|
},
|
|
)
|
|
return err
|
|
}
|
|
|
|
// Set up permission fields
|
|
updatePermissionFields(chat1.ChatID, "user1", "team1")
|
|
updatePermissionFields(chat2.ChatID, "user1", "team2")
|
|
updatePermissionFields(chat3.ChatID, "user2", "team1")
|
|
updatePermissionFields(chat4.ChatID, "user2", "team2")
|
|
|
|
t.Run("FilterByUserID", func(t *testing.T) {
|
|
result, err := store.ListChats(types.ChatFilter{
|
|
UserID: "user1",
|
|
Page: 1,
|
|
PageSize: 20,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats by user: %v", err)
|
|
}
|
|
|
|
if len(result.Data) != 2 {
|
|
t.Errorf("Expected 2 chats for user1, got %d", len(result.Data))
|
|
}
|
|
|
|
// Verify all returned chats belong to user1
|
|
for _, chat := range result.Data {
|
|
if chat.Title != "User1 Team1 Chat" && chat.Title != "User1 Team2 Chat" {
|
|
t.Errorf("Unexpected chat title: %s", chat.Title)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("FilterByTeamID", func(t *testing.T) {
|
|
result, err := store.ListChats(types.ChatFilter{
|
|
TeamID: "team1",
|
|
Page: 1,
|
|
PageSize: 20,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats by team: %v", err)
|
|
}
|
|
|
|
if len(result.Data) != 2 {
|
|
t.Errorf("Expected 2 chats for team1, got %d", len(result.Data))
|
|
}
|
|
|
|
// Verify all returned chats belong to team1
|
|
for _, chat := range result.Data {
|
|
if chat.Title != "User1 Team1 Chat" && chat.Title != "User2 Team1 Chat" {
|
|
t.Errorf("Unexpected chat title: %s", chat.Title)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("FilterByUserIDAndTeamID", func(t *testing.T) {
|
|
result, err := store.ListChats(types.ChatFilter{
|
|
UserID: "user1",
|
|
TeamID: "team1",
|
|
Page: 1,
|
|
PageSize: 20,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats by user and team: %v", err)
|
|
}
|
|
|
|
if len(result.Data) != 1 {
|
|
t.Errorf("Expected 1 chat for user1+team1, got %d", len(result.Data))
|
|
}
|
|
|
|
if len(result.Data) > 0 && result.Data[0].Title != "User1 Team1 Chat" {
|
|
t.Errorf("Expected 'User1 Team1 Chat', got '%s'", result.Data[0].Title)
|
|
}
|
|
})
|
|
|
|
t.Run("FilterByUserIDWithOtherFilters", func(t *testing.T) {
|
|
// Combine UserID with Status filter
|
|
result, err := store.ListChats(types.ChatFilter{
|
|
UserID: "user1",
|
|
Status: "active",
|
|
Page: 1,
|
|
PageSize: 20,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats: %v", err)
|
|
}
|
|
|
|
// All user1's chats should be active (default status)
|
|
if len(result.Data) != 2 {
|
|
t.Errorf("Expected 2 active chats for user1, got %d", len(result.Data))
|
|
}
|
|
})
|
|
|
|
t.Run("FilterByTeamIDWithQueryFilter", func(t *testing.T) {
|
|
// Combine TeamID with custom QueryFilter
|
|
result, err := store.ListChats(types.ChatFilter{
|
|
TeamID: "team2",
|
|
Page: 1,
|
|
PageSize: 20,
|
|
QueryFilter: func(qb query.Query) {
|
|
// Additional filter: only chats with "User1" in title
|
|
qb.Where("title", "like", "%User1%")
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats: %v", err)
|
|
}
|
|
|
|
if len(result.Data) != 1 {
|
|
t.Errorf("Expected 1 chat (User1 in team2), got %d", len(result.Data))
|
|
}
|
|
|
|
if len(result.Data) > 0 && result.Data[0].Title != "User1 Team2 Chat" {
|
|
t.Errorf("Expected 'User1 Team2 Chat', got '%s'", result.Data[0].Title)
|
|
}
|
|
})
|
|
|
|
t.Run("FilterByNonExistentUser", func(t *testing.T) {
|
|
result, err := store.ListChats(types.ChatFilter{
|
|
UserID: "nonexistent_user",
|
|
Page: 1,
|
|
PageSize: 20,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats: %v", err)
|
|
}
|
|
|
|
if len(result.Data) != 0 {
|
|
t.Errorf("Expected 0 chats for nonexistent user, got %d", len(result.Data))
|
|
}
|
|
})
|
|
|
|
t.Run("FilterByNonExistentTeam", func(t *testing.T) {
|
|
result, err := store.ListChats(types.ChatFilter{
|
|
TeamID: "nonexistent_team",
|
|
Page: 1,
|
|
PageSize: 20,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats: %v", err)
|
|
}
|
|
|
|
if len(result.Data) != 0 {
|
|
t.Errorf("Expected 0 chats for nonexistent team, got %d", len(result.Data))
|
|
}
|
|
})
|
|
|
|
t.Run("QueryFilterForOrCondition", func(t *testing.T) {
|
|
// Use QueryFilter for complex OR condition:
|
|
// Get chats where user is user1 OR team is team2
|
|
result, err := store.ListChats(types.ChatFilter{
|
|
Page: 1,
|
|
PageSize: 20,
|
|
QueryFilter: func(qb query.Query) {
|
|
qb.Where(func(sub query.Query) {
|
|
sub.Where("__yao_created_by", "user1").
|
|
OrWhere("__yao_team_id", "team2")
|
|
})
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats with OR condition: %v", err)
|
|
}
|
|
|
|
// Should return: user1+team1, user1+team2, user2+team2 = 3 chats
|
|
if len(result.Data) != 3 {
|
|
t.Errorf("Expected 3 chats (user1 OR team2), got %d", len(result.Data))
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestChatCompleteWorkflow tests a complete chat workflow
|
|
func TestChatCompleteWorkflow(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
store, err := xun.NewXun(types.Setting{
|
|
Connector: "default",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create store: %v", err)
|
|
}
|
|
|
|
t.Run("CompleteWorkflow", func(t *testing.T) {
|
|
// 1. Create chat
|
|
chat := &types.Chat{
|
|
AssistantID: "workflow_assistant",
|
|
Title: "Workflow Test Chat",
|
|
Status: "active",
|
|
}
|
|
|
|
err := store.CreateChat(chat)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create chat: %v", err)
|
|
}
|
|
t.Logf("Created chat: %s", chat.ChatID)
|
|
|
|
// 2. Get chat
|
|
retrieved, err := store.GetChat(chat.ChatID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get chat: %v", err)
|
|
}
|
|
if retrieved.Title != "Workflow Test Chat" {
|
|
t.Errorf("Expected title 'Workflow Test Chat', got '%s'", retrieved.Title)
|
|
}
|
|
|
|
// 3. Update chat
|
|
err = store.UpdateChat(chat.ChatID, map[string]interface{}{
|
|
"title": "Updated Workflow Chat",
|
|
"status": "archived",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to update chat: %v", err)
|
|
}
|
|
|
|
// 4. Verify update
|
|
updated, err := store.GetChat(chat.ChatID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get updated chat: %v", err)
|
|
}
|
|
if updated.Title != "Updated Workflow Chat" {
|
|
t.Errorf("Expected title 'Updated Workflow Chat', got '%s'", updated.Title)
|
|
}
|
|
if updated.Status != "archived" {
|
|
t.Errorf("Expected status 'archived', got '%s'", updated.Status)
|
|
}
|
|
|
|
// 5. List chats
|
|
result, err := store.ListChats(types.ChatFilter{
|
|
AssistantID: "workflow_assistant",
|
|
Page: 1,
|
|
PageSize: 20,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to list chats: %v", err)
|
|
}
|
|
|
|
found := false
|
|
for _, c := range result.Data {
|
|
if c.ChatID == chat.ChatID {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("Expected to find chat in list")
|
|
}
|
|
|
|
// 6. Delete chat
|
|
err = store.DeleteChat(chat.ChatID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to delete chat: %v", err)
|
|
}
|
|
|
|
// 7. Verify deletion
|
|
_, err = store.GetChat(chat.ChatID)
|
|
if err == nil {
|
|
t.Error("Expected error when getting deleted chat")
|
|
}
|
|
|
|
t.Log("Complete workflow passed!")
|
|
})
|
|
}
|