- 15.4.2:管理 API 与 Bearer 共用同一 Authorize 路径,X-API-Key 命中签发表即过门;未知/已吊销/空值 401 且不回落 Bearer(fail-closed) - 15.4.3:key 表去掉角色维度,任一有效 key 鉴权后即 owner;签发接口取消 role 分级(不再读请求体) - 10.4.5:新增 GET/PUT /api/v1/admin/owner-profile(owner-only,PUT 带审计 owner.profile.update),0600 原子落盘 + 活档案热替换;前端主人档案改接真实 API,读不到即禁用保存,删掉硬编码默认档案 - 08.4.2:persona 的 identity.systemPrompt 必填,缺失/空白 400 显式报错,不再派生回落
133 lines
3.2 KiB
Go
133 lines
3.2 KiB
Go
package auth
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/config"
|
|
)
|
|
|
|
func newTestAPIKey() *APIKey {
|
|
return NewAPIKey(config.AuthConfig{})
|
|
}
|
|
|
|
func TestAPIKey_GenerateVerify(t *testing.T) {
|
|
t.Parallel()
|
|
k := newTestAPIKey()
|
|
key, err := k.Generate()
|
|
if err != nil {
|
|
t.Fatalf("Generate error: %v", err)
|
|
}
|
|
if key == "" {
|
|
t.Fatal("Generate returned empty key")
|
|
}
|
|
if !k.Verify(key) {
|
|
t.Errorf("Verify(%q) = false, want true", key)
|
|
}
|
|
// 裁决 15.4.3:任何有效 key 都是超管级,不按 role 分档。
|
|
role, ok := k.Lookup(key)
|
|
if !ok || role != RoleOwner {
|
|
t.Errorf("Lookup(%q) = (%q,%v), want (owner,true)", key, role, ok)
|
|
}
|
|
}
|
|
|
|
// TestAPIKey_GenerateIgnoresLegacyRole 钉住「key 不做角色分级」这条裁决的
|
|
// 可观察后果:即使调用方沿旧签名传了 admin/viewer,签出来的 key 仍然是
|
|
// 超管级,且表里不留任何角色维度。
|
|
func TestAPIKey_GenerateIgnoresLegacyRole(t *testing.T) {
|
|
t.Parallel()
|
|
k := newTestAPIKey()
|
|
for _, want := range []Role{RoleAdmin, RoleViewer} {
|
|
key, err := k.Generate(want)
|
|
if err != nil {
|
|
t.Fatalf("Generate(%q) error: %v", want, err)
|
|
}
|
|
role, ok := k.Lookup(key)
|
|
if !ok || role != RoleOwner {
|
|
t.Fatalf("Generate(%q) → Lookup = (%q,%v), want (owner,true)", want, role, ok)
|
|
}
|
|
}
|
|
// 列表也一律如实报 owner,面板据此没有分级可显示。
|
|
for _, s := range k.List() {
|
|
if s.Role != RoleOwner {
|
|
t.Fatalf("List() entry role = %q, want owner", s.Role)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAPIKey_GenerateUnique(t *testing.T) {
|
|
t.Parallel()
|
|
k := newTestAPIKey()
|
|
a, err := k.Generate()
|
|
if err != nil {
|
|
t.Fatalf("Generate error: %v", err)
|
|
}
|
|
b, err := k.Generate()
|
|
if err != nil {
|
|
t.Fatalf("Generate error: %v", err)
|
|
}
|
|
if a == b {
|
|
t.Fatal("two keys are identical, random generation ineffective")
|
|
}
|
|
}
|
|
|
|
func TestAPIKey_GenerateInvalidRole(t *testing.T) {
|
|
t.Parallel()
|
|
k := newTestAPIKey()
|
|
if _, err := k.Generate(Role("god")); !errors.Is(err, ErrInvalidRole) {
|
|
t.Fatalf("Generate invalid role err = %v, want ErrInvalidRole", err)
|
|
}
|
|
}
|
|
|
|
func TestAPIKey_VerifyUnknown(t *testing.T) {
|
|
t.Parallel()
|
|
k := newTestAPIKey()
|
|
if k.Verify("not-a-key") {
|
|
t.Error("Verify('not-a-key') = true, want false")
|
|
}
|
|
if k.Verify("") {
|
|
t.Error("Verify('') = true, want false")
|
|
}
|
|
}
|
|
|
|
func TestAPIKey_Revoke(t *testing.T) {
|
|
t.Parallel()
|
|
k := newTestAPIKey()
|
|
key, err := k.Generate()
|
|
if err != nil {
|
|
t.Fatalf("Generate error: %v", err)
|
|
}
|
|
if !k.Verify(key) {
|
|
t.Fatalf("Verify before revoke = false, want true")
|
|
}
|
|
k.Revoke(key)
|
|
if k.Verify(key) {
|
|
t.Error("Verify after revoke = true, want false")
|
|
}
|
|
}
|
|
|
|
func TestAPIKey_RevokeAll(t *testing.T) {
|
|
t.Parallel()
|
|
k := newTestAPIKey()
|
|
keys := make([]string, 0, 3)
|
|
for range 3 {
|
|
key, err := k.Generate()
|
|
if err != nil {
|
|
t.Fatalf("Generate error: %v", err)
|
|
}
|
|
keys = append(keys, key)
|
|
}
|
|
if got := k.RevokeAll(); got != 3 {
|
|
t.Fatalf("RevokeAll() = %d, want 3", got)
|
|
}
|
|
for _, key := range keys {
|
|
if k.Verify(key) {
|
|
t.Errorf("Verify(%q) after RevokeAll = true, want false", key)
|
|
}
|
|
}
|
|
// An empty table reports zero and is a safe no-op.
|
|
if got := k.RevokeAll(); got != 0 {
|
|
t.Fatalf("RevokeAll() on empty table = %d, want 0", got)
|
|
}
|
|
}
|