package mcp import ( "encoding/json" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // Scope 的 JSON round-trip 必须保留 nil-vs-empty 语义: // - nil Tools/ProjectIDs = "允许全部" // - empty slice = "全部禁止" // repository.go 的 scopeToJSON / scopeFromJSON 是 encoding/json 的薄封装, // 这里直接测公开类型的 marshal/unmarshal 路径,不耦合具体 helper 实现。 func TestScope_JSON_RoundTrip_Empty(t *testing.T) { t.Parallel() raw, err := json.Marshal(Scope{}) require.NoError(t, err) assert.JSONEq(t, "{}", string(raw), "empty Scope should marshal to {} via omitempty") var got Scope require.NoError(t, json.Unmarshal(raw, &got)) assert.Nil(t, got.Tools, "Tools must remain nil — meaning 'allow all'") assert.Nil(t, got.ProjectIDs, "ProjectIDs must remain nil — meaning 'allow all'") } func TestScope_JSON_RoundTrip_Populated(t *testing.T) { t.Parallel() pid := uuid.New() in := Scope{ Tools: []string{"list_issues", "create_issue"}, ProjectIDs: []uuid.UUID{pid}, } raw, err := json.Marshal(in) require.NoError(t, err) var out Scope require.NoError(t, json.Unmarshal(raw, &out)) assert.Equal(t, in.Tools, out.Tools) assert.Equal(t, in.ProjectIDs, out.ProjectIDs) } func TestScope_NilVsEmpty_AllowsTool(t *testing.T) { t.Parallel() var nilScope *Scope assert.True(t, nilScope.AllowsTool("anything"), "nil receiver allows all") zero := &Scope{} // Tools/ProjectIDs both nil assert.True(t, zero.AllowsTool("anything"), "nil Tools allows all") empty := &Scope{Tools: []string{}} assert.False(t, empty.AllowsTool("anything"), "empty Tools forbids all") allow := &Scope{Tools: []string{"list_issues"}} assert.True(t, allow.AllowsTool("list_issues")) assert.False(t, allow.AllowsTool("create_issue")) } func TestScope_NilVsEmpty_AllowsProject(t *testing.T) { t.Parallel() pidA := uuid.New() pidB := uuid.New() var nilScope *Scope assert.True(t, nilScope.AllowsProject(pidA)) zero := &Scope{} assert.True(t, zero.AllowsProject(pidA), "nil ProjectIDs allows all") empty := &Scope{ProjectIDs: []uuid.UUID{}} assert.False(t, empty.AllowsProject(pidA), "empty ProjectIDs forbids all") allow := &Scope{ProjectIDs: []uuid.UUID{pidA}} assert.True(t, allow.AllowsProject(pidA)) assert.False(t, allow.AllowsProject(pidB)) }