将全局启用、用户组基线、API Key 专属或排除规则及 scope 按固定顺序求值,避免 Key 越过所属用户组权限,并让运行时候选与模型列表共用同一权限链。 新增 Key 级可分配模型与失效规则诊断接口、OpenAI 兼容 /v1/models 及 rich 列表迁移路径;前端权限弹窗改为按当前 Key 实时加载并支持清理失效规则。 验证:Go 全量测试与 go vet 通过;Web 22 个测试文件共 142 项通过;pnpm lint、pnpm openapi、pnpm build、Compose 配置、gofmt、ShellCheck 和 git diff --check 通过;独立 PostgreSQL 真实配置验收通过。
123 lines
3.8 KiB
Go
123 lines
3.8 KiB
Go
package modelaccess
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestScopeAllowsTaskPreservesAliases(t *testing.T) {
|
|
tests := []struct {
|
|
scopes []string
|
|
kind string
|
|
allowed bool
|
|
}{
|
|
{[]string{"chat"}, "responses", true},
|
|
{[]string{"text_generate"}, "chat.completions", true},
|
|
{[]string{"image"}, "images.vectorize", true},
|
|
{[]string{"video"}, "videos.upscales", true},
|
|
{[]string{"audio_generate"}, "music.generations", true},
|
|
{[]string{"text_to_speech"}, "speech.generations", true},
|
|
{[]string{"image"}, "chat.completions", false},
|
|
{nil, "chat.completions", true},
|
|
}
|
|
for _, test := range tests {
|
|
if got := ScopeAllowsTask(test.scopes, test.kind); got != test.allowed {
|
|
t.Fatalf("ScopeAllowsTask(%v, %q) = %v, want %v", test.scopes, test.kind, got, test.allowed)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFilterModelTypesUsesCapabilityScopes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
scopes []string
|
|
types []string
|
|
want []string
|
|
}{
|
|
{"image", []string{"image"}, []string{"text_generate", "image_generate", "image_edit"}, []string{"image_generate", "image_edit"}},
|
|
{"video alias", []string{"video"}, []string{"image_to_video", "video_enhance", "text_generate"}, []string{"image_to_video", "video_enhance"}},
|
|
{"custom exact", []string{"custom_type"}, []string{"custom_type", "other_type"}, []string{"custom_type"}},
|
|
{"all", []string{"all"}, []string{"text_generate", "unknown"}, []string{"text_generate", "unknown"}},
|
|
{"legacy empty", nil, []string{"text_generate", "unknown"}, []string{"text_generate", "unknown"}},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if got := FilterModelTypes(test.scopes, test.types); !reflect.DeepEqual(got, test.want) {
|
|
t.Fatalf("FilterModelTypes(%v, %v) = %v, want %v", test.scopes, test.types, got, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKnownTaskKindsUseExpectedScopes(t *testing.T) {
|
|
tests := []struct {
|
|
kind string
|
|
scope string
|
|
}{
|
|
{"chat.completions", "chat"},
|
|
{"responses", "text_generate"},
|
|
{"embeddings", "text_embedding"},
|
|
{"reranks", "text_rerank"},
|
|
{"images.generations", "image"},
|
|
{"images.edits", "image"},
|
|
{"images.vectorize", "vectorize"},
|
|
{"videos.generations", "video"},
|
|
{"videos.upscales", "video_upscale"},
|
|
{"song.generations", "song"},
|
|
{"music.generations", "music_generate"},
|
|
{"speech.generations", "tts"},
|
|
{"voice.clone", "audio"},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.kind, func(t *testing.T) {
|
|
if !ScopeAllowsTask([]string{test.scope}, test.kind) {
|
|
t.Fatalf("scope %q should allow task %q", test.scope, test.kind)
|
|
}
|
|
if ScopeAllowsTask([]string{"unrelated"}, test.kind) {
|
|
t.Fatalf("unrelated scope allowed task %q", test.kind)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKnownModelTypesUseExpectedScopes(t *testing.T) {
|
|
tests := []struct {
|
|
modelType string
|
|
scope string
|
|
}{
|
|
{"text_generate", "chat"},
|
|
{"tools_call", "text"},
|
|
{"text_embedding", "embedding"},
|
|
{"text_rerank", "rerank"},
|
|
{"image_generate", "image"},
|
|
{"image_edit", "image"},
|
|
{"image_analysis", "image"},
|
|
{"image_vectorize", "vectorize"},
|
|
{"video_generate", "video"},
|
|
{"image_to_video", "video"},
|
|
{"text_to_video", "video"},
|
|
{"video_edit", "video"},
|
|
{"video_reference", "video"},
|
|
{"video_first_last_frame", "video"},
|
|
{"video_understanding", "video"},
|
|
{"omni_video", "video"},
|
|
{"omni", "video"},
|
|
{"video_enhance", "upscale"},
|
|
{"audio_generate", "music"},
|
|
{"music_generate", "song"},
|
|
{"text_to_speech", "speech"},
|
|
{"audio_understanding", "audio"},
|
|
{"voice_clone", "tts"},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.modelType, func(t *testing.T) {
|
|
if !ScopeAllowsModelType([]string{test.scope}, test.modelType) {
|
|
t.Fatalf("scope %q should allow model type %q", test.scope, test.modelType)
|
|
}
|
|
if ScopeAllowsModelType([]string{"unrelated"}, test.modelType) {
|
|
t.Fatalf("unrelated scope allowed model type %q", test.modelType)
|
|
}
|
|
})
|
|
}
|
|
}
|