Merge pull request #1004 from trheyi/main
Add system model loading functionality for testing
This commit is contained in:
commit
a798c44279
3 changed files with 276 additions and 1 deletions
80
mcp/mcp.go
Normal file
80
mcp/mcp.go
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
package mcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/yaoapp/gou/application"
|
||||||
|
"github.com/yaoapp/gou/mcp"
|
||||||
|
"github.com/yaoapp/kun/log"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
"github.com/yaoapp/yao/dsl"
|
||||||
|
"github.com/yaoapp/yao/dsl/types"
|
||||||
|
"github.com/yaoapp/yao/share"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Load load MCP clients
|
||||||
|
func Load(cfg config.Config) error {
|
||||||
|
messages := []string{}
|
||||||
|
|
||||||
|
// Load filesystem MCP clients
|
||||||
|
exts := []string{"*.mcp.yao", "*.mcp.json", "*.mcp.jsonc"}
|
||||||
|
err := application.App.Walk("mcps", func(root, file string, isdir bool) error {
|
||||||
|
if isdir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err := mcp.LoadClient(file, share.ID(root, file))
|
||||||
|
if err != nil {
|
||||||
|
messages = append(messages, err.Error())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}, exts...)
|
||||||
|
|
||||||
|
if len(messages) > 0 {
|
||||||
|
for _, message := range messages {
|
||||||
|
log.Error("Load filesystem MCP clients error: %s", message)
|
||||||
|
}
|
||||||
|
return fmt.Errorf(strings.Join(messages, ";\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load database MCP clients (ignore error)
|
||||||
|
errs := loadDatabaseMCPs()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
for _, err := range errs {
|
||||||
|
log.Error("Load database MCP clients error: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadDatabaseMCPs load database MCP clients
|
||||||
|
func loadDatabaseMCPs() []error {
|
||||||
|
var errs []error = []error{}
|
||||||
|
manager, err := dsl.New(types.TypeMCPClient)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
mcps, err := manager.List(ctx, &types.ListOptions{Store: types.StoreTypeDB, Source: true})
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load MCP clients
|
||||||
|
for _, info := range mcps {
|
||||||
|
_, err := mcp.LoadClientSource(info.Source, info.ID)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
129
mcp/mcp_test.go
Normal file
129
mcp/mcp_test.go
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
package mcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/yaoapp/gou/mcp"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
"github.com/yaoapp/yao/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoad(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
|
||||||
|
err := Load(config.Conf)
|
||||||
|
// Load may fail due to configuration issues, but we should still check what was loaded
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("Load returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
check(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func check(t *testing.T) {
|
||||||
|
clients := mcp.ListClients()
|
||||||
|
clientMap := make(map[string]bool)
|
||||||
|
for _, id := range clients {
|
||||||
|
clientMap[id] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Loaded clients: %v", clients)
|
||||||
|
|
||||||
|
// Check if test MCP clients are loaded (they may fail to load due to configuration)
|
||||||
|
if clientMap["test"] {
|
||||||
|
assert.True(t, clientMap["test"], "test MCP client should be loaded")
|
||||||
|
|
||||||
|
// Verify clients can be selected
|
||||||
|
testClient, err := mcp.Select("test")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotNil(t, testClient)
|
||||||
|
|
||||||
|
// Check that clients exist
|
||||||
|
assert.True(t, mcp.Exists("test"))
|
||||||
|
t.Logf("test MCP client loaded successfully")
|
||||||
|
} else {
|
||||||
|
t.Logf("test MCP client not loaded (possibly due to configuration issues)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if clientMap["http_test"] {
|
||||||
|
assert.True(t, clientMap["http_test"], "http_test MCP client should be loaded")
|
||||||
|
|
||||||
|
httpTestClient, err := mcp.Select("http_test")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotNil(t, httpTestClient)
|
||||||
|
|
||||||
|
assert.True(t, mcp.Exists("http_test"))
|
||||||
|
t.Logf("http_test MCP client loaded successfully")
|
||||||
|
} else {
|
||||||
|
t.Logf("http_test MCP client not loaded (possibly due to configuration issues)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should always be false
|
||||||
|
assert.False(t, mcp.Exists("non_existent"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadWithError(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
|
||||||
|
// Test loading with invalid configuration
|
||||||
|
// This may fail due to configuration issues but shouldn't crash
|
||||||
|
err := Load(config.Conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("Load returned expected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetClient(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
|
||||||
|
err := Load(config.Conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("Load returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test getting existing client (if it was loaded successfully)
|
||||||
|
if mcp.Exists("test") {
|
||||||
|
client := mcp.GetClient("test")
|
||||||
|
assert.NotNil(t, client)
|
||||||
|
t.Logf("GetClient test passed")
|
||||||
|
} else {
|
||||||
|
t.Logf("test client not loaded, skipping GetClient test")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test getting non-existent client should throw exception
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
mcp.GetClient("non_existent")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnloadClient(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
|
||||||
|
err := Load(config.Conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("Load returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test unloading only if client was loaded
|
||||||
|
if mcp.Exists("test") {
|
||||||
|
// Verify client exists before unloading
|
||||||
|
assert.True(t, mcp.Exists("test"))
|
||||||
|
|
||||||
|
// Unload client
|
||||||
|
mcp.UnloadClient("test")
|
||||||
|
|
||||||
|
// Verify client no longer exists
|
||||||
|
assert.False(t, mcp.Exists("test"))
|
||||||
|
t.Logf("UnloadClient test passed")
|
||||||
|
} else {
|
||||||
|
t.Logf("test client not loaded, skipping UnloadClient test")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that unloading non-existent client doesn't crash
|
||||||
|
mcp.UnloadClient("non_existent")
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/yaoapp/gou/api"
|
"github.com/yaoapp/gou/api"
|
||||||
"github.com/yaoapp/gou/application"
|
"github.com/yaoapp/gou/application"
|
||||||
"github.com/yaoapp/gou/connector"
|
"github.com/yaoapp/gou/connector"
|
||||||
|
|
@ -19,8 +20,10 @@ import (
|
||||||
v8 "github.com/yaoapp/gou/runtime/v8"
|
v8 "github.com/yaoapp/gou/runtime/v8"
|
||||||
"github.com/yaoapp/gou/server/http"
|
"github.com/yaoapp/gou/server/http"
|
||||||
"github.com/yaoapp/kun/exception"
|
"github.com/yaoapp/kun/exception"
|
||||||
|
"github.com/yaoapp/kun/log"
|
||||||
"github.com/yaoapp/xun/capsule"
|
"github.com/yaoapp/xun/capsule"
|
||||||
"github.com/yaoapp/yao/config"
|
"github.com/yaoapp/yao/config"
|
||||||
|
"github.com/yaoapp/yao/data"
|
||||||
"github.com/yaoapp/yao/fs"
|
"github.com/yaoapp/yao/fs"
|
||||||
"github.com/yaoapp/yao/helper"
|
"github.com/yaoapp/yao/helper"
|
||||||
"github.com/yaoapp/yao/runtime"
|
"github.com/yaoapp/yao/runtime"
|
||||||
|
|
@ -30,6 +33,63 @@ import (
|
||||||
|
|
||||||
var testServer *http.Server = nil
|
var testServer *http.Server = nil
|
||||||
|
|
||||||
|
// SystemModels system models for testing
|
||||||
|
var testSystemModels = map[string]string{
|
||||||
|
"__yao.assistant": "yao/models/assistant.mod.yao",
|
||||||
|
"__yao.attachment": "yao/models/attachment.mod.yao",
|
||||||
|
"__yao.audit": "yao/models/audit.mod.yao",
|
||||||
|
"__yao.chat": "yao/models/chat.mod.yao",
|
||||||
|
"__yao.config": "yao/models/config.mod.yao",
|
||||||
|
"__yao.dsl": "yao/models/dsl.mod.yao",
|
||||||
|
"__yao.history": "yao/models/history.mod.yao",
|
||||||
|
"__yao.kb": "yao/models/kb.mod.yao",
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadSystemModels load system models for testing
|
||||||
|
func loadSystemModels(t *testing.T, cfg config.Config) error {
|
||||||
|
for id, path := range testSystemModels {
|
||||||
|
content, err := data.Read(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse model
|
||||||
|
var data map[string]interface{}
|
||||||
|
err = application.Parse(path, content, &data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set prefix
|
||||||
|
if table, ok := data["table"].(map[string]interface{}); ok {
|
||||||
|
if name, ok := table["name"].(string); ok {
|
||||||
|
table["name"] = share.App.Prefix + name
|
||||||
|
content, err = jsoniter.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("failed to marshal model data: %v", err)
|
||||||
|
return fmt.Errorf("failed to marshal model data: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load Model
|
||||||
|
mod, err := model.LoadSource(content, id, filepath.Join("__system", path))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("load system model %s error: %s", id, err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto migrate
|
||||||
|
err = mod.Migrate(false, model.WithDonotInsertValues(true))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("migrate system model %s error: %s", id, err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare test environment
|
// Prepare test environment
|
||||||
func Prepare(t *testing.T, cfg config.Config, rootEnv ...string) {
|
func Prepare(t *testing.T, cfg config.Config, rootEnv ...string) {
|
||||||
|
|
||||||
|
|
@ -259,8 +319,14 @@ func loadModel(t *testing.T, cfg config.Config) {
|
||||||
model.WithCrypt([]byte(fmt.Sprintf(`{"key":"%s"}`, cfg.DB.AESKey)), "AES")
|
model.WithCrypt([]byte(fmt.Sprintf(`{"key":"%s"}`, cfg.DB.AESKey)), "AES")
|
||||||
model.WithCrypt([]byte(`{}`), "PASSWORD")
|
model.WithCrypt([]byte(`{}`), "PASSWORD")
|
||||||
|
|
||||||
|
// Load system models
|
||||||
|
err := loadSystemModels(t, cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
exts := []string{"*.mod.yao", "*.mod.json", "*.mod.jsonc"}
|
exts := []string{"*.mod.yao", "*.mod.json", "*.mod.jsonc"}
|
||||||
err := application.App.Walk("models", func(root, file string, isdir bool) error {
|
err = application.App.Walk("models", func(root, file string, isdir bool) error {
|
||||||
if isdir {
|
if isdir {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue