Files
ttstd-infinite-canvas/backend/internal/service/style_profile_test.go

106 lines
4.0 KiB
Go

package service
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), t.TempDir())
}
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)}
}