选择性整合 #547,保留主分支节点与工具条设计。新增默认折叠的节点、资产、任务和历史面板,风格与预设保留在节点内,移除重复入口。 补齐工具权限、提示词解析、资源引用保护及数据库迁移,修复提示词清空和连续编辑后的生成配置同步。 验证:前端全量 1925 项测试、构建、类型检查及 lint 通过;后端 go test ./... 通过。真实模型、PostgreSQL 实库与登录态页面验收仍待进行。 Refs #547 Co-authored-by: bensharp <bensharp@qq.com>
101 lines
3.3 KiB
Go
101 lines
3.3 KiB
Go
package tools_test
|
|
|
|
import (
|
|
"infinite-canvas/backend/internal/database"
|
|
"infinite-canvas/backend/internal/model"
|
|
"infinite-canvas/backend/internal/repository"
|
|
"infinite-canvas/backend/internal/tools"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestToolsVisibilityTokensSeedAndReferences(t *testing.T) {
|
|
db, err := database.Open(database.Config{Driver: "sqlite", DSN: filepath.Join(t.TempDir(), "tools.db")})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sqlDB, _ := db.DB()
|
|
defer sqlDB.Close()
|
|
if err = database.MigrateSchema(db); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
repo := repository.New(db)
|
|
svc := tools.New(repo)
|
|
for range 2 {
|
|
if err := tools.EnsureBuiltinTools(repo); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
list, err := svc.List("alice", tools.ToolListRequest{PageSize: 80})
|
|
if err != nil || list.TotalCount != 87 || !list.HasMore {
|
|
t.Fatalf("seed/pagination: %+v %v", list, err)
|
|
}
|
|
custom, err := svc.Create("alice", tools.ToolMutationRequest{Type: "style", Label: "private", Prompt: "private style"})
|
|
if err != nil || custom.ID <= 87 {
|
|
t.Fatalf("custom: %+v %v", custom, err)
|
|
}
|
|
if _, err := svc.Detail("bob", custom.ID); err == nil {
|
|
t.Fatal("private tool leaked")
|
|
}
|
|
if _, err := svc.SetFavorite("bob", custom.ID, true); err == nil {
|
|
t.Fatal("private favorite accepted")
|
|
}
|
|
if err := svc.Delete("bob", custom.ID); err == nil {
|
|
t.Fatal("cross-user delete accepted")
|
|
}
|
|
if _, err := svc.SetFavorite("alice", custom.ID, true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := svc.SetFavorite("alice", custom.ID, true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, scope := range []string{"", "public", "favorites", "custom"} {
|
|
got, err := svc.List("bob", tools.ToolListRequest{Scope: scope, Search: "private"})
|
|
if err != nil || got.TotalCount != 0 {
|
|
t.Fatalf("scope %q leaked: %+v %v", scope, got, err)
|
|
}
|
|
}
|
|
valid := "scene @[tool:style:1:label:Palette]"
|
|
expanded, err := svc.ResolveToolMentionTokens("alice", "image", valid)
|
|
if err != nil || strings.Contains(expanded, "@[tool:") {
|
|
t.Fatalf("expand: %s %v", expanded, err)
|
|
}
|
|
for _, token := range []string{"@[tool:style:0:x:X]", "@[tool:effect:1:x:X]", "@[tool:style:999999:x:X]", "@[tool:bogus:1:x:X]", "@[tool:style:1:broken]"} {
|
|
if _, err := svc.ResolveToolMentionTokens("alice", "image", token); err == nil {
|
|
t.Fatalf("accepted %s", token)
|
|
}
|
|
}
|
|
if _, err := svc.ResolveToolMentionTokens("alice", "video", valid); err == nil {
|
|
t.Fatal("wrong mode accepted")
|
|
}
|
|
if _, err := svc.ResolveToolMentionTokens("", "image", valid); err == nil {
|
|
t.Fatal("anonymous accepted")
|
|
}
|
|
if err := db.Model(&model.Tool{}).Where("id = ?", 1).Update("enabled", false).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := svc.ResolveToolMentionTokens("alice", "image", valid); err == nil {
|
|
t.Fatal("disabled accepted")
|
|
}
|
|
if err := svc.Delete("alice", custom.ID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var count int64
|
|
db.Model(&model.ToolFavorite{}).Where("tool_id = ?", custom.ID).Count(&count)
|
|
if count != 0 {
|
|
t.Fatal("orphan favorites")
|
|
}
|
|
if err := db.Model(&model.Tool{}).Where("id = ?", 1).Updates(map[string]any{"source": "user", "owner_id": "alice", "label": "keep"}).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := tools.EnsureBuiltinTools(repo); err == nil {
|
|
t.Fatal("seed collision accepted")
|
|
}
|
|
var preserved model.Tool
|
|
db.First(&preserved, 1)
|
|
if preserved.Label != "keep" {
|
|
t.Fatal("user tool overwritten")
|
|
}
|
|
}
|