- 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.
221 lines
6.2 KiB
Go
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)
|
|
}
|
|
}
|