Ctrl K
ComunidadGuías…

model/api_token_unified_scope_test.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package model

import (
	"slices"
	"testing"
)

// 这些测试约束「scope 命名统一」契约:
//   - 只有 nezha:* 一套是 first-class scope;
//   - mcp:* 不再作为 HasScope 的别名(避免 mcp:fs:write 静默扩到 REST 的 nezha:server:write);
//   - AllScopes / AdminOnlyScopes 不再包含 mcp:*,新建 token 不能再签发它们。
//
// 旧 mcp:* 兼容由 createAPIToken 入口做一次性归一化(mcp:fs:read 等只读/exec 映射到
// 对应的 nezha:* read/exec),但 write/delete 类不再映射;详见 controller.createAPIToken。

func TestAllScopes_DoesNotExposeLegacyMCPScopes(t *testing.T) {
	legacy := []string{
		"mcp:*",
		"mcp:server:read",
		"mcp:server:exec",
		"mcp:fs:read",
		"mcp:fs:write",
		"mcp:fs:delete",
	}
	for _, s := range legacy {
		if slices.Contains(AllScopes, s) {
			t.Errorf("AllScopes must not advertise legacy scope %q; only nezha:* is first-class", s)
		}
		if slices.Contains(AdminOnlyScopes, s) {
			t.Errorf("AdminOnlyScopes must not advertise legacy scope %q", s)
		}
	}
}

func TestHasScope_LegacyMCPNoLongerAliasesNezhaWrite(t *testing.T) {
	// 旧 token 数据库里残留 mcp:fs:write,绝不允许覆盖 REST 的 nezha:server:write。
	tok := &APIToken{ScopesCSV: "mcp:fs:write"}
	if tok.HasScope(ScopeServerWrite) {
		t.Fatalf("legacy mcp:fs:write must NOT grant nezha:server:write via HasScope; " +
			"REST routes (server/config, server/:id, batch-delete/server) would become reachable")
	}
	if tok.HasScope(ScopeServerDelete) {
		t.Fatalf("legacy mcp:fs:write must NOT grant nezha:server:delete")
	}
}

func TestHasScope_LegacyMCPDeleteNoLongerAliasesNezhaDelete(t *testing.T) {
	tok := &APIToken{ScopesCSV: "mcp:fs:delete"}
	if tok.HasScope(ScopeServerDelete) {
		t.Fatalf("legacy mcp:fs:delete must NOT grant nezha:server:delete via HasScope")
	}
}

func TestHasScope_LegacyMCPAllNoLongerWildcards(t *testing.T) {
	tok := &APIToken{ScopesCSV: "mcp:*"}
	for _, s := range []string{ScopeServerRead, ScopeServerWrite, ScopeServerDelete, ScopeServerExec} {
		if tok.HasScope(s) {
			t.Errorf("legacy mcp:* must not be treated as a nezha:* wildcard; granted %s", s)
		}
	}
}

func TestHasScope_NezhaWildcardStillWorks(t *testing.T) {
	tok := &APIToken{ScopesCSV: ScopeNezhaAll}
	for _, s := range []string{ScopeServerRead, ScopeServerWrite, ScopeServerDelete, ScopeServerExec} {
		if !tok.HasScope(s) {
			t.Errorf("nezha:* wildcard must still cover %s", s)
		}
	}
}

func TestHasScope_NezhaResourceWildcardStillWorks(t *testing.T) {
	tok := &APIToken{ScopesCSV: "nezha:server:*"}
	for _, s := range []string{ScopeServerRead, ScopeServerWrite, ScopeServerDelete, ScopeServerExec} {
		if !tok.HasScope(s) {
			t.Errorf("nezha:server:* must cover %s", s)
		}
	}
	if tok.HasScope(ScopeServiceRead) {
		t.Fatalf("nezha:server:* must NOT leak into nezha:service:* family")
	}
}