chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,584 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/github/github-mcp-server/internal/githubv4mock"
|
||||
"github.com/github/github-mcp-server/internal/toolsnaps"
|
||||
"github.com/github/github-mcp-server/pkg/translations"
|
||||
"github.com/google/go-github/v89/github"
|
||||
"github.com/google/jsonschema-go/jsonschema"
|
||||
"github.com/shurcooL/githubv4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// recorderTransport routes HTTP requests through an in-process handler, mirroring
|
||||
// internal/githubv4mock's own transport. We need it because githubv4mock keys its
|
||||
// matchers by query string, so it cannot model a multi-page labels query: every
|
||||
// page issues the identical query and differs only by the $cursor variable. This
|
||||
// transport lets a single handler answer each page dynamically.
|
||||
type recorderTransport struct{ handler http.Handler }
|
||||
|
||||
func (rt recorderTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
rec := httptest.NewRecorder()
|
||||
rt.handler.ServeHTTP(rec, req)
|
||||
return rec.Result(), nil
|
||||
}
|
||||
|
||||
// alwaysHasNextPageLabelsClient returns a GraphQL client whose labels query always
|
||||
// reports another page, advancing the cursor on each call. It exercises uiGetLabels'
|
||||
// page cap: the loop fetches one label per page until it stops at uiGetMaxPages with
|
||||
// has_more=true. totalCount is reported as a large server-side count so the test can
|
||||
// confirm it stays the full repo count even when results are truncated.
|
||||
func alwaysHasNextPageLabelsClient(t *testing.T) *http.Client {
|
||||
t.Helper()
|
||||
var calls int
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/graphql", func(w http.ResponseWriter, _ *http.Request) {
|
||||
calls++
|
||||
resp := map[string]any{
|
||||
"data": map[string]any{
|
||||
"repository": map[string]any{
|
||||
"labels": map[string]any{
|
||||
"nodes": []any{
|
||||
map[string]any{
|
||||
"id": fmt.Sprintf("label-%d", calls),
|
||||
"name": fmt.Sprintf("label-%d", calls),
|
||||
"color": "ededed",
|
||||
"description": "",
|
||||
},
|
||||
},
|
||||
"totalCount": 9999,
|
||||
"pageInfo": map[string]any{
|
||||
"hasNextPage": true,
|
||||
"endCursor": fmt.Sprintf("cursor-%d", calls),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(resp)
|
||||
})
|
||||
return &http.Client{Transport: recorderTransport{handler: mux}}
|
||||
}
|
||||
|
||||
// alwaysNextPageHandler returns a REST handler that always advertises another page
|
||||
// via the Link header, regardless of the page requested. It drives a pagination loop
|
||||
// purely off the page cap so tests can assert ui_get stops at uiGetMaxPages and sets
|
||||
// has_more=true. The same body is returned for every page, so the number of items
|
||||
// collected equals the number of pages fetched.
|
||||
func alwaysNextPageHandler(t *testing.T, body any) http.HandlerFunc {
|
||||
t.Helper()
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
page := 1
|
||||
if p := r.URL.Query().Get("page"); p != "" {
|
||||
if parsed, err := strconv.Atoi(p); err == nil {
|
||||
page = parsed
|
||||
}
|
||||
}
|
||||
w.Header().Set("Link", fmt.Sprintf(`<https://api.github.com/next?page=%d>; rel="next"`, page+1))
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_ = json.NewEncoder(w).Encode(body)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_UIGet(t *testing.T) {
|
||||
// Verify tool definition
|
||||
serverTool := UIGet(translations.NullTranslationHelper)
|
||||
tool := serverTool.Tool
|
||||
require.NoError(t, toolsnaps.Test(tool.Name, tool))
|
||||
|
||||
assert.Equal(t, "ui_get", tool.Name)
|
||||
assert.NotEmpty(t, tool.Description)
|
||||
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "method")
|
||||
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "owner")
|
||||
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "repo")
|
||||
assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"method", "owner"})
|
||||
assert.True(t, tool.Annotations.ReadOnlyHint, "ui_get should be read-only")
|
||||
assert.Equal(t, MCPAppsFeatureFlag, serverTool.FeatureFlagEnable, "ui_get should be gated on the MCP Apps feature flag")
|
||||
|
||||
// ui_get must be app-only so the host hides it from the agent's tool list
|
||||
// while keeping it callable by the views (MCP Apps 2026-01-26 spec).
|
||||
ui, ok := tool.Meta["ui"].(map[string]any)
|
||||
require.True(t, ok, "ui_get should declare _meta.ui")
|
||||
assert.Equal(t, []string{"app"}, ui["visibility"], "ui_get should be app-only")
|
||||
|
||||
// Setup mock data
|
||||
mockAssignees := []*github.User{
|
||||
{Login: github.Ptr("user1"), AvatarURL: github.Ptr("https://avatars.githubusercontent.com/u/1")},
|
||||
{Login: github.Ptr("user2"), AvatarURL: github.Ptr("https://avatars.githubusercontent.com/u/2")},
|
||||
}
|
||||
|
||||
mockBranches := []*github.Branch{
|
||||
{Name: github.Ptr("main"), Protected: github.Ptr(true)},
|
||||
{Name: github.Ptr("feature"), Protected: github.Ptr(false)},
|
||||
}
|
||||
|
||||
dueDate := time.Date(2026, 1, 31, 0, 0, 0, 0, time.UTC)
|
||||
mockMilestones := []*github.Milestone{
|
||||
{Number: github.Ptr(1), Title: github.Ptr("with due date"), DueOn: &github.Timestamp{Time: dueDate}},
|
||||
{Number: github.Ptr(2), Title: github.Ptr("no due date")},
|
||||
}
|
||||
|
||||
mockIssueTypes := []*github.IssueType{
|
||||
{Name: github.Ptr("Bug")},
|
||||
{Name: github.Ptr("Feature")},
|
||||
}
|
||||
|
||||
mockReviewers := []*github.User{
|
||||
{Login: github.Ptr("octocat"), AvatarURL: github.Ptr("https://avatars.githubusercontent.com/u/583231")},
|
||||
{Login: github.Ptr("dependabot[bot]"), AvatarURL: github.Ptr("https://avatars.githubusercontent.com/in/29110")},
|
||||
{Login: github.Ptr("github-actions"), Type: github.Ptr("Bot")},
|
||||
}
|
||||
|
||||
mockReviewerTeams := []*github.Team{
|
||||
{Slug: github.Ptr("docs"), Name: github.Ptr("Docs")},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
mockedClient *http.Client
|
||||
mockedGQLClient *http.Client
|
||||
requestArgs map[string]any
|
||||
expectError bool
|
||||
expectedErrMsg string
|
||||
validateResult func(t *testing.T, responseText string)
|
||||
}{
|
||||
{
|
||||
name: "successful assignees fetch",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
"GET /repos/owner/repo/assignees": mockResponse(t, http.StatusOK, mockAssignees),
|
||||
}),
|
||||
requestArgs: map[string]any{
|
||||
"method": "assignees",
|
||||
"owner": "owner",
|
||||
"repo": "repo",
|
||||
},
|
||||
expectError: false,
|
||||
validateResult: func(t *testing.T, responseText string) {
|
||||
var response map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(responseText), &response))
|
||||
assert.Contains(t, response, "assignees")
|
||||
assert.Contains(t, response, "totalCount")
|
||||
assert.Equal(t, false, response["has_more"], "results within the page cap should not be truncated")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful branches fetch",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
"GET /repos/owner/repo/branches": mockResponse(t, http.StatusOK, mockBranches),
|
||||
}),
|
||||
requestArgs: map[string]any{
|
||||
"method": "branches",
|
||||
"owner": "owner",
|
||||
"repo": "repo",
|
||||
},
|
||||
expectError: false,
|
||||
validateResult: func(t *testing.T, responseText string) {
|
||||
var response map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(responseText), &response))
|
||||
assert.Contains(t, response, "branches")
|
||||
assert.Contains(t, response, "totalCount")
|
||||
assert.Equal(t, false, response["has_more"], "results within the page cap should not be truncated")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful milestones fetch",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
"GET /repos/owner/repo/milestones": mockResponse(t, http.StatusOK, mockMilestones),
|
||||
}),
|
||||
requestArgs: map[string]any{
|
||||
"method": "milestones",
|
||||
"owner": "owner",
|
||||
"repo": "repo",
|
||||
},
|
||||
expectError: false,
|
||||
validateResult: func(t *testing.T, responseText string) {
|
||||
var response map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(responseText), &response))
|
||||
milestones, ok := response["milestones"].([]any)
|
||||
require.True(t, ok, "milestones should be a list")
|
||||
require.Len(t, milestones, 2)
|
||||
first := milestones[0].(map[string]any)
|
||||
assert.Equal(t, "2026-01-31", first["due_on"], "milestone with a due date should be formatted")
|
||||
second := milestones[1].(map[string]any)
|
||||
assert.Equal(t, "", second["due_on"], "milestone without a due date should be empty, not zero time")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful issue_types fetch",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
"GET /orgs/owner/issue-types": mockResponse(t, http.StatusOK, mockIssueTypes),
|
||||
}),
|
||||
requestArgs: map[string]any{
|
||||
"method": "issue_types",
|
||||
"owner": "owner",
|
||||
},
|
||||
expectError: false,
|
||||
validateResult: func(t *testing.T, responseText string) {
|
||||
var issueTypes []map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(responseText), &issueTypes))
|
||||
require.Len(t, issueTypes, 2)
|
||||
assert.Equal(t, "Bug", issueTypes[0]["name"])
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "issue_types API error returns response context",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
"GET /orgs/owner/issue-types": mockResponse(t, http.StatusForbidden, map[string]string{"message": "Forbidden"}),
|
||||
}),
|
||||
requestArgs: map[string]any{
|
||||
"method": "issue_types",
|
||||
"owner": "owner",
|
||||
},
|
||||
expectError: true,
|
||||
expectedErrMsg: "failed to list issue types",
|
||||
},
|
||||
{
|
||||
name: "successful labels fetch",
|
||||
mockedGQLClient: githubv4mock.NewMockedHTTPClient(
|
||||
githubv4mock.NewQueryMatcher(
|
||||
struct {
|
||||
Repository struct {
|
||||
Labels struct {
|
||||
Nodes []struct {
|
||||
ID githubv4.ID
|
||||
Name githubv4.String
|
||||
Color githubv4.String
|
||||
Description githubv4.String
|
||||
}
|
||||
TotalCount githubv4.Int
|
||||
PageInfo struct {
|
||||
HasNextPage githubv4.Boolean
|
||||
EndCursor githubv4.String
|
||||
}
|
||||
} `graphql:"labels(first: 100, after: $cursor)"`
|
||||
} `graphql:"repository(owner: $owner, name: $repo)"`
|
||||
}{},
|
||||
map[string]any{
|
||||
"owner": githubv4.String("owner"),
|
||||
"repo": githubv4.String("repo"),
|
||||
"cursor": (*githubv4.String)(nil),
|
||||
},
|
||||
githubv4mock.DataResponse(map[string]any{
|
||||
"repository": map[string]any{
|
||||
"labels": map[string]any{
|
||||
"nodes": []any{
|
||||
map[string]any{
|
||||
"id": githubv4.ID("label-1"),
|
||||
"name": githubv4.String("bug"),
|
||||
"color": githubv4.String("d73a4a"),
|
||||
"description": githubv4.String("Something isn't working"),
|
||||
},
|
||||
},
|
||||
"totalCount": githubv4.Int(1),
|
||||
"pageInfo": map[string]any{
|
||||
"hasNextPage": githubv4.Boolean(false),
|
||||
"endCursor": githubv4.String(""),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
requestArgs: map[string]any{
|
||||
"method": "labels",
|
||||
"owner": "owner",
|
||||
"repo": "repo",
|
||||
},
|
||||
expectError: false,
|
||||
validateResult: func(t *testing.T, responseText string) {
|
||||
var response map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(responseText), &response))
|
||||
labels, ok := response["labels"].([]any)
|
||||
require.True(t, ok, "labels should be a list")
|
||||
require.Len(t, labels, 1)
|
||||
assert.Equal(t, "bug", labels[0].(map[string]any)["name"])
|
||||
assert.Equal(t, float64(1), response["totalCount"])
|
||||
assert.Equal(t, false, response["has_more"], "results within the page cap should not be truncated")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful issue_fields fetch",
|
||||
mockedGQLClient: githubv4mock.NewMockedHTTPClient(
|
||||
githubv4mock.NewQueryMatcher(
|
||||
issueFieldsRepoQuery{},
|
||||
map[string]any{
|
||||
"owner": githubv4.String("owner"),
|
||||
"name": githubv4.String("repo"),
|
||||
},
|
||||
githubv4mock.DataResponse(map[string]any{
|
||||
"repository": map[string]any{
|
||||
"issueFields": map[string]any{
|
||||
"nodes": []any{
|
||||
map[string]any{
|
||||
"__typename": "IssueFieldText",
|
||||
"id": "IFT_1",
|
||||
"fullDatabaseId": "42",
|
||||
"name": "DRI",
|
||||
"description": "Directly responsible individual",
|
||||
"dataType": "text",
|
||||
"visibility": "ORG_ONLY",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
requestArgs: map[string]any{
|
||||
"method": "issue_fields",
|
||||
"owner": "owner",
|
||||
"repo": "repo",
|
||||
},
|
||||
expectError: false,
|
||||
validateResult: func(t *testing.T, responseText string) {
|
||||
var response map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(responseText), &response))
|
||||
fields, ok := response["fields"].([]any)
|
||||
require.True(t, ok, "fields should be a list")
|
||||
require.Len(t, fields, 1)
|
||||
assert.Equal(t, "DRI", fields[0].(map[string]any)["name"])
|
||||
assert.Equal(t, "text", fields[0].(map[string]any)["data_type"])
|
||||
assert.Equal(t, float64(1), response["totalCount"])
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful reviewers fetch",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
"GET /repos/owner/repo/collaborators": mockResponse(t, http.StatusOK, mockReviewers),
|
||||
"GET /repos/owner/repo/teams": mockResponse(t, http.StatusOK, mockReviewerTeams),
|
||||
}),
|
||||
requestArgs: map[string]any{
|
||||
"method": "reviewers",
|
||||
"owner": "owner",
|
||||
"repo": "repo",
|
||||
},
|
||||
expectError: false,
|
||||
validateResult: func(t *testing.T, responseText string) {
|
||||
var response map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(responseText), &response))
|
||||
users, ok := response["users"].([]any)
|
||||
require.True(t, ok, "users should be a list")
|
||||
require.Len(t, users, 1)
|
||||
assert.Equal(t, "octocat", users[0].(map[string]any)["login"])
|
||||
teams, ok := response["teams"].([]any)
|
||||
require.True(t, ok, "teams should be a list")
|
||||
require.Len(t, teams, 1)
|
||||
assert.Equal(t, "docs", teams[0].(map[string]any)["slug"])
|
||||
assert.Equal(t, "owner", teams[0].(map[string]any)["org"])
|
||||
assert.Equal(t, float64(2), response["totalCount"])
|
||||
assert.Equal(t, false, response["has_more"], "results within the page cap should not be truncated")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "branches pagination stops at the page cap",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
"GET /repos/owner/repo/branches": alwaysNextPageHandler(t, []*github.Branch{{Name: github.Ptr("feature")}}),
|
||||
}),
|
||||
requestArgs: map[string]any{
|
||||
"method": "branches",
|
||||
"owner": "owner",
|
||||
"repo": "repo",
|
||||
},
|
||||
expectError: false,
|
||||
validateResult: func(t *testing.T, responseText string) {
|
||||
var response map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(responseText), &response))
|
||||
branches, ok := response["branches"].([]any)
|
||||
require.True(t, ok, "branches should be a list")
|
||||
assert.Len(t, branches, uiGetMaxPages, "loop should stop at the page cap")
|
||||
assert.Equal(t, float64(uiGetMaxPages), response["totalCount"], "totalCount should be the bounded count")
|
||||
assert.Equal(t, true, response["has_more"], "truncated results should set has_more")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "labels pagination stops at the page cap",
|
||||
mockedGQLClient: alwaysHasNextPageLabelsClient(t),
|
||||
requestArgs: map[string]any{
|
||||
"method": "labels",
|
||||
"owner": "owner",
|
||||
"repo": "repo",
|
||||
},
|
||||
expectError: false,
|
||||
validateResult: func(t *testing.T, responseText string) {
|
||||
var response map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(responseText), &response))
|
||||
labels, ok := response["labels"].([]any)
|
||||
require.True(t, ok, "labels should be a list")
|
||||
assert.Len(t, labels, uiGetMaxPages, "loop should stop at the page cap")
|
||||
assert.Equal(t, true, response["has_more"], "truncated results should set has_more")
|
||||
// totalCount stays the server-reported full count, so it can exceed
|
||||
// the number of labels returned once results are truncated.
|
||||
assert.Equal(t, float64(9999), response["totalCount"])
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reviewers pagination stops at the page cap",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
"GET /repos/owner/repo/collaborators": alwaysNextPageHandler(t, []*github.User{{Login: github.Ptr("octocat")}}),
|
||||
"GET /repos/owner/repo/teams": mockResponse(t, http.StatusOK, mockReviewerTeams),
|
||||
}),
|
||||
requestArgs: map[string]any{
|
||||
"method": "reviewers",
|
||||
"owner": "owner",
|
||||
"repo": "repo",
|
||||
},
|
||||
expectError: false,
|
||||
validateResult: func(t *testing.T, responseText string) {
|
||||
var response map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(responseText), &response))
|
||||
users, ok := response["users"].([]any)
|
||||
require.True(t, ok, "users should be a list")
|
||||
assert.Len(t, users, uiGetMaxPages, "collaborators loop should stop at the page cap")
|
||||
assert.Equal(t, true, response["has_more"], "truncating either loop should set has_more")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing method parameter",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
|
||||
requestArgs: map[string]any{
|
||||
"owner": "owner",
|
||||
"repo": "repo",
|
||||
},
|
||||
expectError: true,
|
||||
expectedErrMsg: "missing required parameter: method",
|
||||
},
|
||||
{
|
||||
name: "missing owner parameter",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
|
||||
requestArgs: map[string]any{
|
||||
"method": "assignees",
|
||||
"repo": "repo",
|
||||
},
|
||||
expectError: true,
|
||||
expectedErrMsg: "missing required parameter: owner",
|
||||
},
|
||||
{
|
||||
name: "missing repo parameter for assignees",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
|
||||
requestArgs: map[string]any{
|
||||
"method": "assignees",
|
||||
"owner": "owner",
|
||||
},
|
||||
expectError: true,
|
||||
expectedErrMsg: "missing required parameter: repo",
|
||||
},
|
||||
{
|
||||
name: "unknown method",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
|
||||
requestArgs: map[string]any{
|
||||
"method": "unknown",
|
||||
"owner": "owner",
|
||||
"repo": "repo",
|
||||
},
|
||||
expectError: true,
|
||||
expectedErrMsg: "unknown method: unknown",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Setup deps with REST and/or GraphQL mocks
|
||||
deps := BaseDeps{}
|
||||
if tc.mockedClient != nil {
|
||||
client, err := github.NewClient(github.WithHTTPClient(tc.mockedClient))
|
||||
require.NoError(t, err)
|
||||
deps.Client = client
|
||||
}
|
||||
if tc.mockedGQLClient != nil {
|
||||
deps.GQLClient = githubv4.NewClient(tc.mockedGQLClient)
|
||||
}
|
||||
handler := serverTool.Handler(deps)
|
||||
|
||||
// Create call request
|
||||
request := createMCPRequest(tc.requestArgs)
|
||||
|
||||
// Call handler
|
||||
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
|
||||
|
||||
// Verify results
|
||||
if tc.expectError {
|
||||
if err != nil {
|
||||
assert.Contains(t, err.Error(), tc.expectedErrMsg)
|
||||
return
|
||||
}
|
||||
require.NotNil(t, result)
|
||||
require.True(t, result.IsError)
|
||||
errorContent := getErrorResult(t, result)
|
||||
assert.Contains(t, errorContent.Text, tc.expectedErrMsg)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
require.False(t, result.IsError)
|
||||
textContent := getTextResult(t, result)
|
||||
|
||||
if tc.validateResult != nil {
|
||||
tc.validateResult(t, textContent.Text)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_marshalUIGetIssueFields_TrimsForUI(t *testing.T) {
|
||||
priorityLow := 1
|
||||
priorityHigh := 2
|
||||
result, _, err := marshalUIGetIssueFields([]IssueField{
|
||||
{
|
||||
ID: "field-1",
|
||||
DatabaseID: 123,
|
||||
Name: "Priority",
|
||||
Description: "How urgent this is",
|
||||
DataType: "single_select",
|
||||
Visibility: "public",
|
||||
Options: []IssueSingleSelectFieldOption{
|
||||
{ID: "option-2", Name: "High", Description: "High priority", Color: "red", Priority: &priorityHigh},
|
||||
{ID: "option-1", Name: "Low", Description: "Low priority", Color: "blue", Priority: &priorityLow},
|
||||
{ID: "option-3", Name: "No priority", Description: "No priority set", Color: "gray"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "field-2",
|
||||
Name: "Unsupported",
|
||||
DataType: "iteration",
|
||||
},
|
||||
{
|
||||
ID: "field-3",
|
||||
Name: "Notes",
|
||||
DataType: "text",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var response map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(getTextResult(t, result).Text), &response))
|
||||
fields := response["fields"].([]any)
|
||||
require.Len(t, fields, 2)
|
||||
assert.Equal(t, float64(2), response["totalCount"])
|
||||
|
||||
singleSelectField := fields[0].(map[string]any)
|
||||
assert.NotContains(t, singleSelectField, "full_database_id")
|
||||
assert.NotContains(t, singleSelectField, "visibility")
|
||||
options := singleSelectField["options"].([]any)
|
||||
require.Len(t, options, 3)
|
||||
assert.Equal(t, "Low", options[0].(map[string]any)["name"])
|
||||
assert.Equal(t, "High", options[1].(map[string]any)["name"])
|
||||
assert.Equal(t, "No priority", options[2].(map[string]any)["name"])
|
||||
assert.NotContains(t, options[0].(map[string]any), "id")
|
||||
assert.NotContains(t, options[0].(map[string]any), "priority")
|
||||
|
||||
textField := fields[1].(map[string]any)
|
||||
assert.NotContains(t, textField, "options")
|
||||
}
|
||||
Reference in New Issue
Block a user