yao/agent/search/handlers/db/handler_integration_test.go
Max 1d965420ad Add integration tests for graceful handling of nonexistent and partially existing models in search
- Introduced new test cases to validate the behavior of the search handler when querying with nonexistent models, ensuring it returns appropriate error messages without panicking.
- Added a test for scenarios where only some of the requested models exist, confirming that the search can still succeed with valid models while handling errors gracefully.
- Updated the search handler to improve error handling by checking for model existence before proceeding with the search, enhancing robustness in search operations.
2025-12-18 12:20:20 +08:00

221 lines
6.2 KiB
Go

package db_test
import (
"testing"
"github.com/yaoapp/gou/model"
"github.com/yaoapp/gou/query/gou"
"github.com/yaoapp/yao/agent/context"
"github.com/yaoapp/yao/agent/search/handlers/db"
"github.com/yaoapp/yao/agent/search/types"
"github.com/yaoapp/yao/agent/testutils"
oauthTypes "github.com/yaoapp/yao/openapi/oauth/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// ============================================================================
// Integration Tests - Requires database and models
// ============================================================================
func TestHandler_Search_Integration(t *testing.T) {
// Skip if running short tests
if testing.Short() {
t.Skip("Skipping integration test")
}
// Initialize test environment (loads models, database, query engine, etc.)
testutils.Prepare(t)
defer testutils.Clean(t)
// Create test context
ctx := newTestContext(t)
// Verify __yao.role model is loaded
mod := model.Select("__yao.role")
require.NotNil(t, mod, "__yao.role model should be loaded")
t.Run("search_role_model_with_results", func(t *testing.T) {
// First, ensure there's at least one role in the database
ensureTestRole(t, mod)
// Create handler with builtin QueryDSL generator
h := db.NewHandler("builtin", &types.DBConfig{
Models: []string{"__yao.role"},
MaxResults: 10,
})
req := &types.Request{
Type: types.SearchTypeDB,
Query: "查询所有角色",
Source: types.SourceAuto,
Models: []string{"__yao.role"},
Scenario: types.ScenarioFilter,
Limit: 10,
}
result, err := h.SearchWithContext(ctx, req)
require.NoError(t, err)
require.NotNil(t, result)
// Verify result structure
assert.Equal(t, types.SearchTypeDB, result.Type)
assert.Equal(t, "查询所有角色", result.Query)
assert.Equal(t, types.SourceAuto, result.Source)
assert.GreaterOrEqual(t, result.Duration, int64(0))
// Should have results
if result.Error != "" {
t.Logf("Search error: %s", result.Error)
}
assert.Empty(t, result.Error, "Search should not return error")
assert.Greater(t, len(result.Items), 0, "Should have at least one result")
assert.Equal(t, len(result.Items), result.Total)
// Verify result items
for _, item := range result.Items {
assert.Equal(t, types.SearchTypeDB, item.Type)
assert.Equal(t, types.SourceAuto, item.Source)
assert.Equal(t, "__yao.role", item.Model)
assert.NotNil(t, item.Data, "Data should not be nil")
assert.NotNil(t, item.RecordID, "RecordID should not be nil")
}
})
t.Run("search_with_filter_scenario", func(t *testing.T) {
h := db.NewHandler("builtin", nil)
req := &types.Request{
Type: types.SearchTypeDB,
Query: "查询系统角色",
Source: types.SourceHook,
Models: []string{"__yao.role"},
Scenario: types.ScenarioFilter,
Limit: 5,
}
result, err := h.SearchWithContext(ctx, req)
require.NoError(t, err)
require.NotNil(t, result)
assert.Equal(t, types.SearchTypeDB, result.Type)
assert.Equal(t, types.SourceHook, result.Source)
assert.LessOrEqual(t, len(result.Items), 5, "Should respect limit")
})
t.Run("search_with_preset_wheres", func(t *testing.T) {
h := db.NewHandler("builtin", nil)
req := &types.Request{
Type: types.SearchTypeDB,
Query: "查询角色",
Source: types.SourceAuto,
Models: []string{"__yao.role"},
Wheres: []gou.Where{
{Condition: gou.Condition{Field: &gou.Expression{Field: "is_active"}, Value: true, OP: "="}},
},
Limit: 10,
}
result, err := h.SearchWithContext(ctx, req)
require.NoError(t, err)
require.NotNil(t, result)
// All results should have is_active = true (due to preset where)
for _, item := range result.Items {
if data, ok := item.Data["is_active"]; ok {
// is_active could be bool or int depending on driver
switch v := data.(type) {
case bool:
assert.True(t, v)
case int64:
assert.Equal(t, int64(1), v)
case float64:
assert.Equal(t, float64(1), v)
}
}
}
})
t.Run("search_nonexistent_model_graceful", func(t *testing.T) {
h := db.NewHandler("builtin", nil)
req := &types.Request{
Type: types.SearchTypeDB,
Query: "查询文章",
Source: types.SourceAuto,
Models: []string{"nonexistent_model", "article", "fake_model"},
Limit: 10,
}
// Should NOT panic, should return gracefully with error
result, err := h.SearchWithContext(ctx, req)
require.NoError(t, err)
require.NotNil(t, result)
// Should have error message about no valid models
assert.Equal(t, types.SearchTypeDB, result.Type)
assert.Equal(t, "no valid models found", result.Error)
assert.Empty(t, result.Items)
})
t.Run("search_mixed_models_partial_exist", func(t *testing.T) {
h := db.NewHandler("builtin", nil)
req := &types.Request{
Type: types.SearchTypeDB,
Query: "查询角色",
Source: types.SourceAuto,
Models: []string{"nonexistent_model", "__yao.role", "fake_model"}, // Only __yao.role exists
Limit: 10,
}
// Should NOT panic, should work with the existing model
result, err := h.SearchWithContext(ctx, req)
require.NoError(t, err)
require.NotNil(t, result)
// Should succeed with partial models
assert.Equal(t, types.SearchTypeDB, result.Type)
if result.Error == "" {
// If no error, should have results from __yao.role
assert.GreaterOrEqual(t, len(result.Items), 0)
}
})
}
// newTestContext creates a test context with required fields
func newTestContext(t *testing.T) *context.Context {
t.Helper()
authorized := &oauthTypes.AuthorizedInfo{
UserID: "test-user",
}
chatID := "test-chat-db-search"
ctx := context.New(t.Context(), authorized, chatID)
return ctx
}
// ensureTestRole ensures there's at least one role in the database for testing
func ensureTestRole(t *testing.T, mod *model.Model) {
t.Helper()
// Try to find existing roles
rows, err := mod.Get(model.QueryParam{Limit: 1})
if err == nil && len(rows) > 0 {
return // Already have roles
}
// Create a test role
_, err = mod.Create(map[string]interface{}{
"role_id": "test_role",
"name": "Test Role",
"description": "A test role for unit testing",
"is_active": true,
"is_system": false,
"level": 1,
})
if err != nil {
t.Logf("Note: Could not create test role: %v", err)
}
}