106 lines
4.0 KiB
Go
106 lines
4.0 KiB
Go
package prompts
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"infinite-canvas/backend/internal/model"
|
|
"infinite-canvas/backend/internal/repository"
|
|
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func TestStyleProfileCreateAndUpdateOwnsIdentityAndRevision(t *testing.T) {
|
|
svc := newStyleProfileTestService(t)
|
|
created, err := svc.CreateStyleProfile("user-1", styleProfileTestRequest(t, "志怪暗彩", 88))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var createdDocument StyleProfileDocument
|
|
if err := json.Unmarshal([]byte(created.ProfileJSON), &createdDocument); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if created.Revision != 1 || createdDocument.PresetID != created.ID || createdDocument.SourceProfileID != created.ID || createdDocument.Source != "user" || createdDocument.Revision != 1 {
|
|
t.Fatalf("created profile identity was not normalized: model=%+v document=%+v", created, createdDocument)
|
|
}
|
|
if createdDocument.Selection["medium"] != "工笔暗彩" || len(createdDocument.Assets) != 1 || createdDocument.Assets[0].License == nil || createdDocument.Assets[0].License.Note != "需保留来源记录" {
|
|
t.Fatalf("created profile metadata was not preserved: document=%+v", createdDocument)
|
|
}
|
|
|
|
updated, err := svc.UpdateStyleProfile("user-1", created.ID, styleProfileTestRequest(t, "志怪暗彩二版", 300))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var updatedDocument StyleProfileDocument
|
|
if err := json.Unmarshal([]byte(updated.ProfileJSON), &updatedDocument); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if updated.Revision != 2 || updatedDocument.Revision != 2 || updatedDocument.PresetID != created.ID || updatedDocument.Title != "志怪暗彩二版" {
|
|
t.Fatalf("updated profile revision was not server controlled: model=%+v document=%+v", updated, updatedDocument)
|
|
}
|
|
}
|
|
|
|
func TestStyleProfileMutationsRejectOtherUsers(t *testing.T) {
|
|
svc := newStyleProfileTestService(t)
|
|
created, err := svc.CreateStyleProfile("user-1", styleProfileTestRequest(t, "用户一风格", 1))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := svc.UpdateStyleProfile("user-2", created.ID, styleProfileTestRequest(t, "越权修改", 1)); err == nil {
|
|
t.Fatal("expected cross-user update to fail")
|
|
}
|
|
if err := svc.SetStyleProfileFavorite("user-2", created.ID, true); err == nil {
|
|
t.Fatal("expected cross-user favorite to fail")
|
|
}
|
|
if err := svc.TouchStyleProfile("user-2", created.ID); err == nil {
|
|
t.Fatal("expected cross-user use tracking to fail")
|
|
}
|
|
if err := svc.DeleteStyleProfile("user-2", created.ID); err == nil {
|
|
t.Fatal("expected cross-user delete to fail")
|
|
}
|
|
profiles, err := svc.ListStyleProfiles("user-1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(profiles) != 1 || profiles[0].ID != created.ID {
|
|
t.Fatalf("owner profile was changed by rejected mutations: %+v", profiles)
|
|
}
|
|
}
|
|
|
|
func newStyleProfileTestService(t *testing.T) *Service {
|
|
t.Helper()
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sqlDB, err := db.DB()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sqlDB.SetMaxOpenConns(1)
|
|
if err := db.AutoMigrate(&model.StyleProfile{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return New(repository.New(db), nil)
|
|
}
|
|
|
|
func styleProfileTestRequest(t *testing.T, title string, revision int) StyleProfileRequest {
|
|
t.Helper()
|
|
document := StyleProfileDocument{
|
|
SchemaVersion: 1, PresetID: "client-controlled-id", Title: title, Description: "可复用风格",
|
|
Tags: []string{"志怪", "暗彩"}, Prompt: "统一使用工笔线条、暗彩矿物色和克制光影。", NegativePrompt: "禁止媒介漂移和随机文字。",
|
|
Selection: map[string]string{"world": "东方志怪", "medium": "工笔暗彩"},
|
|
Assets: []StyleProfileAsset{{
|
|
ID: "license-snapshot", Kind: "prompt", Provider: "manual", Title: "材质约束", PromptFragment: "矿物色与宣纸肌理", Status: "validated",
|
|
License: &StyleProfileAssetLicense{Note: "需保留来源记录", Source: "内部规范"},
|
|
}},
|
|
ExecutionPolicy: "compatible-fallback", Source: "external", Revision: revision,
|
|
}
|
|
encoded, err := json.Marshal(document)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return StyleProfileRequest{ProfileJSON: string(encoded)}
|
|
}
|