Files
easyai-ai-gateway/apps/api/internal/modelaccess/scopes_test.go
T
wangbo cc97e6649c fix(access): 统一 API Key 模型权限与列表契约
将全局启用、用户组基线、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 真实配置验收通过。
2026-08-03 09:17:15 +08:00

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)
}
})
}
}