Files
ttstd-infinite-canvas/backend/internal/handler/short_drama_feature_routes_test.go
Catlaxy AI 28f2362e3e fix(handler): 短剧开关 - 关闭短剧时保留角色库、画风与声音接口 (#736)
短剧功能开关原本挂在整个 RegisterProjectRoutes 上,连画布共用的 /characters、
/style-profiles、/voice-profiles 也一起返回 403,导致关闭短剧后画布的角色库、我的画风和声音不可用。
将这三类个人资料库路由拆到 RegisterLibraryRoutes,在开关之外注册;/projects 系列仍受开关控制,登录校验不变。

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:03:57 +08:00

101 lines
3.7 KiB
Go

package handler
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"yingce/backend/internal/auth"
"yingce/backend/internal/model"
"yingce/backend/internal/repository"
"yingce/backend/internal/service"
"github.com/gin-gonic/gin"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// 角色库、我的画风、声音是画布与短剧共用的个人资料库,短剧关闭时不能一起被拦截;
// /projects 系列接口仍受短剧开关控制,且两类接口都必须先登录。
func TestShortDramaFeatureGateKeepsSharedLibraries(t *testing.T) {
gin.SetMode(gin.TestMode)
for _, enabled := range []bool{false, true} {
t.Run(fmt.Sprintf("shortDramaEnabled=%t", enabled), func(t *testing.T) {
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)
t.Cleanup(func() { _ = sqlDB.Close() })
if err := db.AutoMigrate(&model.User{}, &model.AuthSession{}, &model.SystemSetting{},
&model.Asset{}, &model.AssetVersion{}, &model.AssetRepresentation{}, &model.CharacterVoiceBinding{},
&model.VoiceProfile{}, &model.StyleProfile{}, &model.Project{}, &model.ProjectAssetLink{},
&model.ProjectAssetFolder{}, &model.Resource{}, &model.Shot{}, &model.ShotAssetReference{},
&model.ProjectUnit{}, &model.CanvasProject{}); err != nil {
t.Fatal(err)
}
for _, record := range []any{
&model.User{ID: "owner", Username: "owner", Email: "owner@example.invalid", Role: model.UserRoleUser, Status: model.UserStatusActive},
&model.AuthSession{ID: "owner", UserID: "owner", TokenHash: auth.HashToken("test-token"), ExpiresAt: time.Now().Add(time.Hour)},
&model.SystemSetting{Key: "feature_availability", ValueJSON: fmt.Sprintf(`{"shortDramaEnabled":%t}`, enabled)},
} {
if err := db.Create(record).Error; err != nil {
t.Fatal(err)
}
}
svc := service.New(repository.New(db), t.TempDir())
character, err := svc.CreateCharacter("owner", service.CreateCharacterRequest{Name: "Existing character"})
if err != nil {
t.Fatal(err)
}
router := gin.New()
RegisterCanvasAPI(router.Group("/api"), svc)
call := func(method, path, body, actor string, want int) {
t.Helper()
req := httptest.NewRequest(method, "/api"+path, strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
if actor != "" {
req.AddCookie(&http.Cookie{Name: service.SessionCookieName, Value: actor + ".test-token"})
}
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != want {
t.Fatalf("%s %s actor=%q: got %d want %d: %s", method, path, actor, w.Code, want, w.Body.String())
}
if want == http.StatusOK {
var envelope struct {
Code int `json:"code"`
}
if err := json.Unmarshal(w.Body.Bytes(), &envelope); err != nil || envelope.Code != 0 {
t.Fatalf("%s %s: unsuccessful envelope: %s", method, path, w.Body.String())
}
}
}
for _, route := range []struct{ method, path, body string }{
{http.MethodGet, "/characters", ""},
{http.MethodGet, "/characters/" + character.Asset.ID, ""},
{http.MethodPost, "/characters", `{"name":"New character"}`},
{http.MethodGet, "/style-profiles", ""},
{http.MethodGet, "/voice-profiles", ""},
} {
call(route.method, route.path, route.body, "", http.StatusUnauthorized)
call(route.method, route.path, route.body, "owner", http.StatusOK)
}
projectWant := http.StatusForbidden
if enabled {
projectWant = http.StatusOK
}
call(http.MethodGet, "/projects", "", "", http.StatusUnauthorized)
call(http.MethodGet, "/projects", "", "owner", projectWant)
})
}
}