diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f1b4cf9cb..e8b79d7e8 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -131,7 +131,8 @@ All workflows run on push/PR unless noted. Located in `.github/workflows/`: - Use `testify` for assertions (`require` for critical checks, `assert` for non-blocking) - Tests are in `*_test.go` files alongside implementation (internal tests, not `_test` package) -- Mock GitHub API with `go-github-mock` (REST) or `githubv4mock` (GraphQL) +- Mock GitHub REST API with the repo's handler-map HTTP helpers (e.g., `MockHTTPClientWithHandlers`) +- Mock GitHub GraphQL API with `githubv4mock` - Test structure for tools: 1. Test tool snapshot 2. Verify critical schema properties (e.g., ReadOnly annotation) diff --git a/docs/testing.md b/docs/testing.md index 226660e9d..f4cafa032 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -7,7 +7,7 @@ This project uses a combination of unit tests and end-to-end (e2e) tests to ensu - Unit tests are located alongside implementation, with filenames ending in `_test.go`. - Currently the preference is to use internal tests i.e. test files do not have `_test` package suffix. - Tests use [testify](https://github.com/stretchr/testify) for assertions and require statements. Use `require` when continuing the test is not meaningful, for example it is almost never correct to continue after an error expectation. -- Mocking is performed using [go-github-mock](https://github.com/migueleliasweb/go-github-mock) or `githubv4mock` for simulating GitHub rest and GQL API responses. +- Mocking is performed using the in-repo HTTP handler-map helpers (e.g. `MockHTTPClientWithHandlers`, `mockResponse`) for GitHub REST, and `githubv4mock` for simulating GitHub GraphQL API responses. - Each tool's schema is snapshotted and checked for changes using the `toolsnaps` utility (see below). - Tests are designed to be explicit and verbose to aid maintainability and clarity. - Handler unit tests should take the form of: diff --git a/go.mod b/go.mod index 9423ce557..d8920c682 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/google/jsonschema-go v0.3.0 github.com/josephburnett/jd v1.9.2 github.com/microcosm-cc/bluemonday v1.0.27 - github.com/migueleliasweb/go-github-mock v1.3.0 github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021 github.com/spf13/cobra v1.10.1 github.com/spf13/viper v1.21.0 @@ -18,9 +17,7 @@ require ( github.com/aymerick/douceur v0.2.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/swag v0.21.1 // indirect - github.com/google/go-github/v71 v71.0.0 // indirect github.com/gorilla/css v1.0.1 // indirect - github.com/gorilla/mux v1.8.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -53,7 +50,6 @@ require ( golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.28.0 // indirect - golang.org/x/time v0.5.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index fc0980ab1..3272aefed 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,6 @@ github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-github/v71 v71.0.0 h1:Zi16OymGKZZMm8ZliffVVJ/Q9YZreDKONCr+WUd0Z30= -github.com/google/go-github/v71 v71.0.0/go.mod h1:URZXObp2BLlMjwu0O8g4y6VBneUj2bCHgnI8FfgZ51M= github.com/google/go-github/v79 v79.0.0 h1:MdodQojuFPBhmtwHiBcIGLw/e/wei2PvFX9ndxK0X4Y= github.com/google/go-github/v79 v79.0.0/go.mod h1:OAFbNhq7fQwohojb06iIIQAB9CBGYLq999myfUFnrS4= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -32,8 +30,6 @@ github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIy github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josephburnett/jd v1.9.2 h1:ECJRRFXCCqbtidkAHckHGSZm/JIaAxS1gygHLF8MI5Y= @@ -55,8 +51,6 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= -github.com/migueleliasweb/go-github-mock v1.3.0 h1:2sVP9JEMB2ubQw1IKto3/fzF51oFC6eVWOOFDgQoq88= -github.com/migueleliasweb/go-github-mock v1.3.0/go.mod h1:ipQhV8fTcj/G6m7BKzin08GaJ/3B5/SonRAkgrk0zCY= github.com/modelcontextprotocol/go-sdk v1.2.0-pre.1 h1:14+JrlEIFvUmbu5+iJzWPLk8CkpvegfKr42oXyjp3O4= github.com/modelcontextprotocol/go-sdk v1.2.0-pre.1/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10= github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021 h1:31Y+Yu373ymebRdJN1cWLLooHH8xAr0MhKTEJGV/87g= @@ -114,8 +108,6 @@ golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/github/actions_test.go b/pkg/github/actions_test.go index f2d336e21..605084ebc 100644 --- a/pkg/github/actions_test.go +++ b/pkg/github/actions_test.go @@ -14,11 +14,10 @@ import ( "github.com/github/github-mcp-server/internal/profiler" "github.com/github/github-mcp-server/internal/toolsnaps" - buffer "github.com/github/github-mcp-server/pkg/buffer" + "github.com/github/github-mcp-server/pkg/buffer" "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v79/github" "github.com/google/jsonschema-go/jsonschema" - "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -88,11 +87,11 @@ func Test_ListWorkflows(t *testing.T) { expectError: false, }, { - name: "missing required parameter owner", - mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), + name: "missing required parameter owner", requestArgs: map[string]any{ "repo": "repo", }, + mockedClient: MockHTTPClientWithHandlers(nil), expectError: true, expectedErrMsg: "missing required parameter: owner", }, @@ -1394,17 +1393,11 @@ func Test_RerunFailedJobs(t *testing.T) { }{ { name: "successful rerun of failed jobs", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/actions/runs/12345/rerun-failed-jobs", - Method: "POST", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusCreated) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposActionsRunsRerunFailedJobsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusCreated) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1414,7 +1407,7 @@ func Test_RerunFailedJobs(t *testing.T) { }, { name: "missing required parameter run_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1466,17 +1459,11 @@ func Test_RerunWorkflowRun_Behavioral(t *testing.T) { }{ { name: "successful rerun of workflow run", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/actions/runs/12345/rerun", - Method: "POST", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusCreated) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposActionsRunsRerunByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusCreated) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1486,7 +1473,7 @@ func Test_RerunWorkflowRun_Behavioral(t *testing.T) { }, { name: "missing required parameter run_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1538,32 +1525,29 @@ func Test_ListWorkflowRuns_Behavioral(t *testing.T) { }{ { name: "successful workflow runs listing", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsWorkflowsRunsByOwnerByRepoByWorkflowId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - runs := &github.WorkflowRuns{ - TotalCount: github.Ptr(2), - WorkflowRuns: []*github.WorkflowRun{ - { - ID: github.Ptr(int64(123)), - Name: github.Ptr("CI"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), - }, - { - ID: github.Ptr(int64(456)), - Name: github.Ptr("CI"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("failure"), - }, + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsWorkflowsRunsByOwnerByRepoByWorkflowID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + runs := &github.WorkflowRuns{ + TotalCount: github.Ptr(2), + WorkflowRuns: []*github.WorkflowRun{ + { + ID: github.Ptr(int64(123)), + Name: github.Ptr("CI"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("success"), }, - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(runs) - }), - ), - ), + { + ID: github.Ptr(int64(456)), + Name: github.Ptr("CI"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("failure"), + }, + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(runs) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1573,7 +1557,7 @@ func Test_ListWorkflowRuns_Behavioral(t *testing.T) { }, { name: "missing required parameter workflow_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1625,21 +1609,18 @@ func Test_GetWorkflowRun_Behavioral(t *testing.T) { }{ { name: "successful get workflow run", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsRunsByOwnerByRepoByRunId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - run := &github.WorkflowRun{ - ID: github.Ptr(int64(12345)), - Name: github.Ptr("CI"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(run) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + run := &github.WorkflowRun{ + ID: github.Ptr(int64(12345)), + Name: github.Ptr("CI"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("success"), + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(run) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1649,7 +1630,7 @@ func Test_GetWorkflowRun_Behavioral(t *testing.T) { }, { name: "missing required parameter run_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1701,15 +1682,12 @@ func Test_GetWorkflowRunLogs_Behavioral(t *testing.T) { }{ { name: "successful get workflow run logs", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsRunsLogsByOwnerByRepoByRunId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Location", "https://github.com/logs/run/12345") - w.WriteHeader(http.StatusFound) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsLogsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Location", "https://github.com/logs/run/12345") + w.WriteHeader(http.StatusFound) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1719,7 +1697,7 @@ func Test_GetWorkflowRunLogs_Behavioral(t *testing.T) { }, { name: "missing required parameter run_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1771,32 +1749,29 @@ func Test_ListWorkflowJobs_Behavioral(t *testing.T) { }{ { name: "successful list workflow jobs", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsRunsJobsByOwnerByRepoByRunId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - jobs := &github.Jobs{ - TotalCount: github.Ptr(2), - Jobs: []*github.WorkflowJob{ - { - ID: github.Ptr(int64(1)), - Name: github.Ptr("build"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), - }, - { - ID: github.Ptr(int64(2)), - Name: github.Ptr("test"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("failure"), - }, + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsJobsByOwnerByRepoByRunID: func(w http.ResponseWriter, _ *http.Request) { + jobs := &github.Jobs{ + TotalCount: github.Ptr(2), + Jobs: []*github.WorkflowJob{ + { + ID: github.Ptr(int64(1)), + Name: github.Ptr("build"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("success"), }, - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(jobs) - }), - ), - ), + { + ID: github.Ptr(int64(2)), + Name: github.Ptr("test"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("failure"), + }, + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(jobs) + }, + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1806,7 +1781,7 @@ func Test_ListWorkflowJobs_Behavioral(t *testing.T) { }, { name: "missing required parameter run_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1873,32 +1848,29 @@ func Test_ActionsList_ListWorkflows(t *testing.T) { }{ { name: "successful workflow list", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsWorkflowsByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - workflows := &github.Workflows{ - TotalCount: github.Ptr(2), - Workflows: []*github.Workflow{ - { - ID: github.Ptr(int64(1)), - Name: github.Ptr("CI"), - Path: github.Ptr(".github/workflows/ci.yml"), - State: github.Ptr("active"), - }, - { - ID: github.Ptr(int64(2)), - Name: github.Ptr("Deploy"), - Path: github.Ptr(".github/workflows/deploy.yml"), - State: github.Ptr("active"), - }, + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsWorkflowsByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + workflows := &github.Workflows{ + TotalCount: github.Ptr(2), + Workflows: []*github.Workflow{ + { + ID: github.Ptr(int64(1)), + Name: github.Ptr("CI"), + Path: github.Ptr(".github/workflows/ci.yml"), + State: github.Ptr("active"), + }, + { + ID: github.Ptr(int64(2)), + Name: github.Ptr("Deploy"), + Path: github.Ptr(".github/workflows/deploy.yml"), + State: github.Ptr("active"), }, - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(workflows) - }), - ), - ), + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(workflows) + }), + }), requestArgs: map[string]any{ "method": "list_workflows", "owner": "owner", @@ -1908,7 +1880,7 @@ func Test_ActionsList_ListWorkflows(t *testing.T) { }, { name: "missing required parameter method", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1952,26 +1924,23 @@ func Test_ActionsList_ListWorkflowRuns(t *testing.T) { toolDef := ActionsList(translations.NullTranslationHelper) t.Run("successful workflow runs list", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsWorkflowsRunsByOwnerByRepoByWorkflowId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - runs := &github.WorkflowRuns{ - TotalCount: github.Ptr(1), - WorkflowRuns: []*github.WorkflowRun{ - { - ID: github.Ptr(int64(123)), - Name: github.Ptr("CI"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), - }, + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsWorkflowsRunsByOwnerByRepoByWorkflowID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + runs := &github.WorkflowRuns{ + TotalCount: github.Ptr(1), + WorkflowRuns: []*github.WorkflowRun{ + { + ID: github.Ptr(int64(123)), + Name: github.Ptr("CI"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("success"), }, - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(runs) - }), - ), - ) + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(runs) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -1998,7 +1967,7 @@ func Test_ActionsList_ListWorkflowRuns(t *testing.T) { }) t.Run("missing resource_id for list_workflow_runs", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient() + mockedClient := MockHTTPClientWithHandlers(nil) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2040,21 +2009,18 @@ func Test_ActionsGet_GetWorkflow(t *testing.T) { toolDef := ActionsGet(translations.NullTranslationHelper) t.Run("successful workflow get", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsWorkflowsByOwnerByRepoByWorkflowId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - workflow := &github.Workflow{ - ID: github.Ptr(int64(1)), - Name: github.Ptr("CI"), - Path: github.Ptr(".github/workflows/ci.yml"), - State: github.Ptr("active"), - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(workflow) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsWorkflowsByOwnerByRepoByWorkflowID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + workflow := &github.Workflow{ + ID: github.Ptr(int64(1)), + Name: github.Ptr("CI"), + Path: github.Ptr(".github/workflows/ci.yml"), + State: github.Ptr("active"), + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(workflow) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2086,21 +2052,18 @@ func Test_ActionsGet_GetWorkflowRun(t *testing.T) { toolDef := ActionsGet(translations.NullTranslationHelper) t.Run("successful workflow run get", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsRunsByOwnerByRepoByRunId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - run := &github.WorkflowRun{ - ID: github.Ptr(int64(12345)), - Name: github.Ptr("CI"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(run) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + run := &github.WorkflowRun{ + ID: github.Ptr(int64(12345)), + Name: github.Ptr("CI"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("success"), + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(run) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2157,14 +2120,11 @@ func Test_ActionsRunTrigger_RunWorkflow(t *testing.T) { }{ { name: "successful workflow run", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposActionsWorkflowsDispatchesByOwnerByRepoByWorkflowId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNoContent) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposActionsWorkflowsDispatchesByOwnerByRepoByWorkflowID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }), + }), requestArgs: map[string]any{ "method": "run_workflow", "owner": "owner", @@ -2176,7 +2136,7 @@ func Test_ActionsRunTrigger_RunWorkflow(t *testing.T) { }, { name: "missing required parameter workflow_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "method": "run_workflow", "owner": "owner", @@ -2188,7 +2148,7 @@ func Test_ActionsRunTrigger_RunWorkflow(t *testing.T) { }, { name: "missing required parameter ref", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "method": "run_workflow", "owner": "owner", @@ -2233,17 +2193,11 @@ func Test_ActionsRunTrigger_CancelWorkflowRun(t *testing.T) { toolDef := ActionsRunTrigger(translations.NullTranslationHelper) t.Run("successful workflow run cancellation", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/actions/runs/12345/cancel", - Method: "POST", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusAccepted) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposActionsRunsCancelByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusAccepted) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2270,17 +2224,11 @@ func Test_ActionsRunTrigger_CancelWorkflowRun(t *testing.T) { }) t.Run("conflict when cancelling a workflow run", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/actions/runs/12345/cancel", - Method: "POST", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusConflict) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposActionsRunsCancelByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusConflict) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2304,7 +2252,7 @@ func Test_ActionsRunTrigger_CancelWorkflowRun(t *testing.T) { }) t.Run("missing run_id for non-run_workflow methods", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient() + mockedClient := MockHTTPClientWithHandlers(nil) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2351,15 +2299,12 @@ func Test_ActionsGetJobLogs_SingleJob(t *testing.T) { toolDef := ActionsGetJobLogs(translations.NullTranslationHelper) t.Run("successful single job logs with URL", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsJobsLogsByOwnerByRepoByJobId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Location", "https://github.com/logs/job/123") - w.WriteHeader(http.StatusFound) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsJobsLogsByOwnerByRepoByJobID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Location", "https://github.com/logs/job/123") + w.WriteHeader(http.StatusFound) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2392,42 +2337,36 @@ func Test_ActionsGetJobLogs_FailedJobs(t *testing.T) { toolDef := ActionsGetJobLogs(translations.NullTranslationHelper) t.Run("successful failed jobs logs", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsRunsJobsByOwnerByRepoByRunId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - jobs := &github.Jobs{ - TotalCount: github.Ptr(3), - Jobs: []*github.WorkflowJob{ - { - ID: github.Ptr(int64(1)), - Name: github.Ptr("test-job-1"), - Conclusion: github.Ptr("success"), - }, - { - ID: github.Ptr(int64(2)), - Name: github.Ptr("test-job-2"), - Conclusion: github.Ptr("failure"), - }, - { - ID: github.Ptr(int64(3)), - Name: github.Ptr("test-job-3"), - Conclusion: github.Ptr("failure"), - }, + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsJobsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + jobs := &github.Jobs{ + TotalCount: github.Ptr(3), + Jobs: []*github.WorkflowJob{ + { + ID: github.Ptr(int64(1)), + Name: github.Ptr("test-job-1"), + Conclusion: github.Ptr("success"), }, - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(jobs) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposActionsJobsLogsByOwnerByRepoByJobId, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Location", "https://github.com/logs/job/"+r.URL.Path[len(r.URL.Path)-1:]) - w.WriteHeader(http.StatusFound) - }), - ), - ) + { + ID: github.Ptr(int64(2)), + Name: github.Ptr("test-job-2"), + Conclusion: github.Ptr("failure"), + }, + { + ID: github.Ptr(int64(3)), + Name: github.Ptr("test-job-3"), + Conclusion: github.Ptr("failure"), + }, + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(jobs) + }), + GetReposActionsJobsLogsByOwnerByRepoByJobID: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", "https://github.com/logs/job/"+r.URL.Path[len(r.URL.Path)-1:]) + w.WriteHeader(http.StatusFound) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2457,30 +2396,27 @@ func Test_ActionsGetJobLogs_FailedJobs(t *testing.T) { }) t.Run("no failed jobs found", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsRunsJobsByOwnerByRepoByRunId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - jobs := &github.Jobs{ - TotalCount: github.Ptr(2), - Jobs: []*github.WorkflowJob{ - { - ID: github.Ptr(int64(1)), - Name: github.Ptr("test-job-1"), - Conclusion: github.Ptr("success"), - }, - { - ID: github.Ptr(int64(2)), - Name: github.Ptr("test-job-2"), - Conclusion: github.Ptr("success"), - }, + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsJobsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + jobs := &github.Jobs{ + TotalCount: github.Ptr(2), + Jobs: []*github.WorkflowJob{ + { + ID: github.Ptr(int64(1)), + Name: github.Ptr("test-job-1"), + Conclusion: github.Ptr("success"), }, - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(jobs) - }), - ), - ) + { + ID: github.Ptr(int64(2)), + Name: github.Ptr("test-job-2"), + Conclusion: github.Ptr("success"), + }, + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(jobs) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ diff --git a/pkg/github/helper_test.go b/pkg/github/helper_test.go index 56a236660..ff3fbfbe1 100644 --- a/pkg/github/helper_test.go +++ b/pkg/github/helper_test.go @@ -11,7 +11,7 @@ import ( "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" + testifymock "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -32,8 +32,8 @@ const ( GetReposTagsByOwnerByRepo = "GET /repos/{owner}/{repo}/tags" GetReposCommitsByOwnerByRepo = "GET /repos/{owner}/{repo}/commits" GetReposCommitsByOwnerByRepoByRef = "GET /repos/{owner}/{repo}/commits/{ref}" - GetReposContentsByOwnerByRepoByPath = "GET /repos/{owner}/{repo}/contents/{path}" - PutReposContentsByOwnerByRepoByPath = "PUT /repos/{owner}/{repo}/contents/{path}" + GetReposContentsByOwnerByRepoByPath = "GET /repos/{owner}/{repo}/contents/{path:.*}" + PutReposContentsByOwnerByRepoByPath = "PUT /repos/{owner}/{repo}/contents/{path:.*}" PostReposForksByOwnerByRepo = "POST /repos/{owner}/{repo}/forks" GetReposSubscriptionByOwnerByRepo = "GET /repos/{owner}/{repo}/subscription" PutReposSubscriptionByOwnerByRepo = "PUT /repos/{owner}/{repo}/subscription" @@ -41,9 +41,9 @@ const ( // Git endpoints GetReposGitTreesByOwnerByRepoByTree = "GET /repos/{owner}/{repo}/git/trees/{tree}" - GetReposGitRefByOwnerByRepoByRef = "GET /repos/{owner}/{repo}/git/ref/{ref}" + GetReposGitRefByOwnerByRepoByRef = "GET /repos/{owner}/{repo}/git/ref/{ref:.*}" PostReposGitRefsByOwnerByRepo = "POST /repos/{owner}/{repo}/git/refs" - PatchReposGitRefsByOwnerByRepoByRef = "PATCH /repos/{owner}/{repo}/git/refs/{ref}" + PatchReposGitRefsByOwnerByRepoByRef = "PATCH /repos/{owner}/{repo}/git/refs/{ref:.*}" GetReposGitCommitsByOwnerByRepoByCommitSHA = "GET /repos/{owner}/{repo}/git/commits/{commit_sha}" PostReposGitCommitsByOwnerByRepo = "POST /repos/{owner}/{repo}/git/commits" GetReposGitTagsByOwnerByRepoByTagSHA = "GET /repos/{owner}/{repo}/git/tags/{tag_sha}" @@ -59,7 +59,7 @@ const ( PatchReposIssuesByOwnerByRepoByIssueNumber = "PATCH /repos/{owner}/{repo}/issues/{issue_number}" GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber = "GET /repos/{owner}/{repo}/issues/{issue_number}/sub_issues" PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber = "POST /repos/{owner}/{repo}/issues/{issue_number}/sub_issues" - DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber = "DELETE /repos/{owner}/{repo}/issues/{issue_number}/sub_issues" + DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber = "DELETE /repos/{owner}/{repo}/issues/{issue_number}/sub_issue" PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber = "PATCH /repos/{owner}/{repo}/issues/{issue_number}/sub_issues/priority" // Pull request endpoints @@ -129,6 +129,26 @@ const ( GetReposActionsJobsLogsByOwnerByRepoByJobID = "GET /repos/{owner}/{repo}/actions/jobs/{job_id}/logs" DeleteReposActionsRunsLogsByOwnerByRepoByRunID = "DELETE /repos/{owner}/{repo}/actions/runs/{run_id}/logs" + // Projects V2 endpoints + GetOrgsProjectsV2 = "GET /orgs/{org}/projectsV2" + GetUsersProjectsV2 = "GET /users/{username}/projectsV2" + GetOrgsProjectsV2ByProjectID = "GET /orgs/{org}/projectsV2/{project_id}" + GetUsersProjectsV2ByProjectID = "GET /users/{username}/projectsV2/{project_id}" + GetOrgsProjectsV2Fields = "GET /orgs/{org}/projectsV2/{project_id}/fields" + GetUsersProjectsV2Fields = "GET /users/{username}/projectsV2/{project_id}/fields" + GetOrgsProjectsV2FieldsByFieldID = "GET /orgs/{org}/projectsV2/{project_id}/fields/{field_id}" + GetUsersProjectsV2FieldsByFieldID = "GET /users/{username}/projectsV2/{project_id}/fields/{field_id}" + GetOrgsProjectsV2Items = "GET /orgs/{org}/projectsV2/{project_id}/items" + GetUsersProjectsV2Items = "GET /users/{username}/projectsV2/{project_id}/items" + GetOrgsProjectsV2ItemsByItemID = "GET /orgs/{org}/projectsV2/{project_id}/items/{item_id}" + GetUsersProjectsV2ItemsByItemID = "GET /users/{username}/projectsV2/{project_id}/items/{item_id}" + PostOrgsProjectsV2Items = "POST /orgs/{org}/projectsV2/{project_id}/items" + PostUsersProjectsV2Items = "POST /users/{username}/projectsV2/{project_id}/items" + PatchOrgsProjectsV2ItemsByItemID = "PATCH /orgs/{org}/projectsV2/{project_id}/items/{item_id}" + PatchUsersProjectsV2ItemsByItemID = "PATCH /users/{username}/projectsV2/{project_id}/items/{item_id}" + DeleteOrgsProjectsV2ItemsByItemID = "DELETE /orgs/{org}/projectsV2/{project_id}/items/{item_id}" + DeleteUsersProjectsV2ItemsByItemID = "DELETE /users/{username}/projectsV2/{project_id}/items/{item_id}" + // Search endpoints GetSearchCode = "GET /search/code" GetSearchIssues = "GET /search/issues" @@ -408,7 +428,7 @@ func getResourceResult(t *testing.T, result *mcp.CallToolResult) *mcp.ResourceCo // MockRoundTripper is a mock HTTP transport using testify/mock type MockRoundTripper struct { - mock.Mock + testifymock.Mock handlers map[string]http.HandlerFunc } diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go index b810cede3..30f5aff0f 100644 --- a/pkg/github/issues_test.go +++ b/pkg/github/issues_test.go @@ -17,7 +17,6 @@ import ( "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v79/github" "github.com/google/jsonschema-go/jsonschema" - "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -3647,7 +3646,7 @@ func Test_ListIssueTypes(t *testing.T) { name: "successful issue types retrieval", mockedClient: mock.NewMockedHTTPClient( mock.WithRequestMatchHandler( - mock.EndpointPattern{ + EndpointPattern{ Pattern: "/orgs/testorg/issue-types", Method: "GET", }, @@ -3664,7 +3663,7 @@ func Test_ListIssueTypes(t *testing.T) { name: "organization not found", mockedClient: mock.NewMockedHTTPClient( mock.WithRequestMatchHandler( - mock.EndpointPattern{ + EndpointPattern{ Pattern: "/orgs/nonexistent/issue-types", Method: "GET", }, @@ -3681,7 +3680,7 @@ func Test_ListIssueTypes(t *testing.T) { name: "missing owner parameter", mockedClient: mock.NewMockedHTTPClient( mock.WithRequestMatchHandler( - mock.EndpointPattern{ + EndpointPattern{ Pattern: "/orgs/testorg/issue-types", Method: "GET", }, diff --git a/pkg/github/mock_shim_test.go b/pkg/github/mock_shim_test.go new file mode 100644 index 000000000..163d3cc39 --- /dev/null +++ b/pkg/github/mock_shim_test.go @@ -0,0 +1,126 @@ +package github + +import ( + "encoding/json" + "net/http" +) + +type requestMatchOption func(map[string]http.HandlerFunc) + +type EndpointPattern struct { + Method string + Pattern string +} + +func endpointKey(endpoint any) string { + switch v := endpoint.(type) { + case string: + return v + case EndpointPattern: + return v.Method + " " + v.Pattern + default: + panic("unsupported endpoint type") + } +} + +func newMockedHTTPClient(opts ...requestMatchOption) *http.Client { + handlers := map[string]http.HandlerFunc{} + for _, opt := range opts { + if opt != nil { + opt(handlers) + } + } + return MockHTTPClientWithHandlers(handlers) +} + +func withRequestMatch(endpoint any, response any) requestMatchOption { + return withRequestMatchHandler(endpoint, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + switch body := response.(type) { + case string: + _, _ = w.Write([]byte(body)) + return + case []byte: + _, _ = w.Write(body) + return + case nil: + return + default: + contentBytes, err := json.Marshal(body) + if err != nil { + panic(err) + } + _, _ = w.Write(contentBytes) + } + })) +} + +func withRequestMatchHandler(endpoint any, handler http.HandlerFunc) requestMatchOption { + return func(handlers map[string]http.HandlerFunc) { + handlers[endpointKey(endpoint)] = handler + } +} + +// mock is a local shim that preserves a legacy callsite style in tests, +// while delegating to the PR #1629 handler-map mock implementation. +var mock = struct { + NewMockedHTTPClient func(...requestMatchOption) *http.Client + WithRequestMatch func(any, any) requestMatchOption + WithRequestMatchHandler func(any, http.HandlerFunc) requestMatchOption + + GetReposIssuesByOwnerByRepoByIssueNumber string + GetSearchIssues string + GetReposIssuesCommentsByOwnerByRepoByIssueNumber string + GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber string + GetReposCommitsStatusesByOwnerByRepoByRef string + GetReposCommitsStatusByOwnerByRepoByRef string + GetReposPullsByOwnerByRepo string + GetReposPullsByOwnerByRepoByPullNumber string + GetReposPullsFilesByOwnerByRepoByPullNumber string + GetReposPullsReviewsByOwnerByRepoByPullNumber string + + PostReposIssuesByOwnerByRepo string + PostReposIssuesCommentsByOwnerByRepoByIssueNumber string + PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber string + PostReposPullsByOwnerByRepo string + PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber string + + PatchReposIssuesByOwnerByRepoByIssueNumber string + PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber string + PatchReposPullsByOwnerByRepoByPullNumber string + + PutReposPullsMergeByOwnerByRepoByPullNumber string + PutReposPullsUpdateBranchByOwnerByRepoByPullNumber string + + DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber string +}{ + NewMockedHTTPClient: newMockedHTTPClient, + WithRequestMatch: withRequestMatch, + WithRequestMatchHandler: withRequestMatchHandler, + + GetReposIssuesByOwnerByRepoByIssueNumber: GetReposIssuesByOwnerByRepoByIssueNumber, + GetSearchIssues: GetSearchIssues, + GetReposIssuesCommentsByOwnerByRepoByIssueNumber: GetReposIssuesCommentsByOwnerByRepoByIssueNumber, + GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber: GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber, + GetReposCommitsStatusesByOwnerByRepoByRef: GetReposCommitsStatusesByOwnerByRepoByRef, + GetReposCommitsStatusByOwnerByRepoByRef: GetReposCommitsStatusByOwnerByRepoByRef, + GetReposPullsByOwnerByRepo: GetReposPullsByOwnerByRepo, + GetReposPullsByOwnerByRepoByPullNumber: GetReposPullsByOwnerByRepoByPullNumber, + GetReposPullsFilesByOwnerByRepoByPullNumber: GetReposPullsFilesByOwnerByRepoByPullNumber, + GetReposPullsReviewsByOwnerByRepoByPullNumber: GetReposPullsReviewsByOwnerByRepoByPullNumber, + + PostReposIssuesByOwnerByRepo: PostReposIssuesByOwnerByRepo, + PostReposIssuesCommentsByOwnerByRepoByIssueNumber: PostReposIssuesCommentsByOwnerByRepoByIssueNumber, + PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber: PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber, + PostReposPullsByOwnerByRepo: PostReposPullsByOwnerByRepo, + PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber: PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber, + + PatchReposIssuesByOwnerByRepoByIssueNumber: PatchReposIssuesByOwnerByRepoByIssueNumber, + PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber: PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber, + PatchReposPullsByOwnerByRepoByPullNumber: PatchReposPullsByOwnerByRepoByPullNumber, + + PutReposPullsMergeByOwnerByRepoByPullNumber: PutReposPullsMergeByOwnerByRepoByPullNumber, + PutReposPullsUpdateBranchByOwnerByRepoByPullNumber: PutReposPullsUpdateBranchByOwnerByRepoByPullNumber, + + DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber: DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber, +} diff --git a/pkg/github/projects_test.go b/pkg/github/projects_test.go index e443b9ecd..53f24d0ea 100644 --- a/pkg/github/projects_test.go +++ b/pkg/github/projects_test.go @@ -11,7 +11,6 @@ import ( "github.com/github/github-mcp-server/pkg/translations" gh "github.com/google/go-github/v79/github" "github.com/google/jsonschema-go/jsonschema" - "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -45,15 +44,9 @@ func Test_ListProjects(t *testing.T) { }{ { name: "success organization", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(orgProjects)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2: mockResponse(t, http.StatusOK, orgProjects), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -63,15 +56,9 @@ func Test_ListProjects(t *testing.T) { }, { name: "success user", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{username}/projectsV2", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(userProjects)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUsersProjectsV2: mockResponse(t, http.StatusOK, userProjects), + }), requestArgs: map[string]interface{}{ "owner": "octocat", "owner_type": "user", @@ -81,21 +68,17 @@ func Test_ListProjects(t *testing.T) { }, { name: "success organization with pagination & query", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - q := r.URL.Query() - if q.Get("per_page") == "50" && q.Get("q") == "roadmap" { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(orgProjects)) - return - } - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message":"unexpected query params"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + if q.Get("per_page") == "50" && q.Get("q") == "roadmap" { + mockResponse(t, http.StatusOK, orgProjects)(w, r) + return + } + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message":"unexpected query params"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -107,12 +90,9 @@ func Test_ListProjects(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2", Method: http.MethodGet}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -122,7 +102,7 @@ func Test_ListProjects(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]interface{}{ "owner_type": "org", }, @@ -130,7 +110,7 @@ func Test_ListProjects(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]interface{}{ "owner": "octo-org", }, @@ -204,12 +184,9 @@ func Test_GetProject(t *testing.T) { }{ { name: "success organization project fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/123", Method: http.MethodGet}, - mockResponse(t, http.StatusOK, project), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ByProjectID: mockResponse(t, http.StatusOK, project), + }), requestArgs: map[string]interface{}{ "project_number": float64(123), "owner": "octo-org", @@ -219,12 +196,9 @@ func Test_GetProject(t *testing.T) { }, { name: "success user project fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{username}/projectsV2/456", Method: http.MethodGet}, - mockResponse(t, http.StatusOK, project), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUsersProjectsV2ByProjectID: mockResponse(t, http.StatusOK, project), + }), requestArgs: map[string]interface{}{ "project_number": float64(456), "owner": "octocat", @@ -234,12 +208,9 @@ func Test_GetProject(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/999", Method: http.MethodGet}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ByProjectID: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]interface{}{ "project_number": float64(999), "owner": "octo-org", @@ -250,7 +221,7 @@ func Test_GetProject(t *testing.T) { }, { name: "missing project_number", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -259,7 +230,7 @@ func Test_GetProject(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]interface{}{ "project_number": float64(123), "owner_type": "org", @@ -268,7 +239,7 @@ func Test_GetProject(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]interface{}{ "project_number": float64(123), "owner": "octo-org", @@ -343,15 +314,9 @@ func Test_ListProjectFields(t *testing.T) { }{ { name: "success organization fields", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/fields", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(orgFields)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2Fields: mockResponse(t, http.StatusOK, orgFields), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -361,21 +326,17 @@ func Test_ListProjectFields(t *testing.T) { }, { name: "success user fields with per_page override", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{user}/projectsV2/{project}/fields", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - q := r.URL.Query() - if q.Get("per_page") == "50" { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(userFields)) - return - } - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message":"unexpected query params"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUsersProjectsV2Fields: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + if q.Get("per_page") == "50" { + mockResponse(t, http.StatusOK, userFields)(w, r) + return + } + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message":"unexpected query params"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "octocat", "owner_type": "user", @@ -386,12 +347,9 @@ func Test_ListProjectFields(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/fields", Method: http.MethodGet}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2Fields: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -402,7 +360,7 @@ func Test_ListProjectFields(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]interface{}{ "owner_type": "org", "project_number": 10, @@ -411,7 +369,7 @@ func Test_ListProjectFields(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]interface{}{ "owner": "octo-org", "project_number": 10, @@ -420,7 +378,7 @@ func Test_ListProjectFields(t *testing.T) { }, { name: "missing project_number", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -500,12 +458,9 @@ func Test_GetProjectField(t *testing.T) { }{ { name: "success organization field", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/fields/{field_id}", Method: http.MethodGet}, - mockResponse(t, http.StatusOK, orgField), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2FieldsByFieldID: mockResponse(t, http.StatusOK, orgField), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -516,12 +471,9 @@ func Test_GetProjectField(t *testing.T) { }, { name: "success user field", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{user}/projectsV2/{project}/fields/{field_id}", Method: http.MethodGet}, - mockResponse(t, http.StatusOK, userField), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUsersProjectsV2FieldsByFieldID: mockResponse(t, http.StatusOK, userField), + }), requestArgs: map[string]any{ "owner": "octocat", "owner_type": "user", @@ -532,12 +484,9 @@ func Test_GetProjectField(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/fields/{field_id}", Method: http.MethodGet}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2FieldsByFieldID: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -549,7 +498,7 @@ func Test_GetProjectField(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner_type": "org", "project_number": float64(10), @@ -559,7 +508,7 @@ func Test_GetProjectField(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "project_number": float64(10), @@ -569,7 +518,7 @@ func Test_GetProjectField(t *testing.T) { }, { name: "missing project_number", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -579,7 +528,7 @@ func Test_GetProjectField(t *testing.T) { }, { name: "missing field_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -671,12 +620,9 @@ func Test_ListProjectItems(t *testing.T) { }{ { name: "success organization items", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items", Method: http.MethodGet}, - mockResponse(t, http.StatusOK, orgItems), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2Items: mockResponse(t, http.StatusOK, orgItems), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -686,21 +632,17 @@ func Test_ListProjectItems(t *testing.T) { }, { name: "success organization items with fields", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - q := r.URL.Query() - if q.Get("fields") == "123,456,789" { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(orgItems)) - return - } - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message":"unexpected query params"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2Items: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + if q.Get("fields") == "123,456,789" { + mockResponse(t, http.StatusOK, orgItems)(w, r) + return + } + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message":"unexpected query params"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -711,12 +653,9 @@ func Test_ListProjectItems(t *testing.T) { }, { name: "success user items", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{user}/projectsV2/{project}/items", Method: http.MethodGet}, - mockResponse(t, http.StatusOK, userItems), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUsersProjectsV2Items: mockResponse(t, http.StatusOK, userItems), + }), requestArgs: map[string]interface{}{ "owner": "octocat", "owner_type": "user", @@ -726,21 +665,17 @@ func Test_ListProjectItems(t *testing.T) { }, { name: "success with pagination and query", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - q := r.URL.Query() - if q.Get("per_page") == "50" && q.Get("q") == "bug" { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(orgItems)) - return - } - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message":"unexpected query params"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2Items: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + if q.Get("per_page") == "50" && q.Get("q") == "bug" { + mockResponse(t, http.StatusOK, orgItems)(w, r) + return + } + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message":"unexpected query params"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -752,12 +687,9 @@ func Test_ListProjectItems(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items", Method: http.MethodGet}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2Items: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -768,7 +700,7 @@ func Test_ListProjectItems(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]interface{}{ "owner_type": "org", "project_number": float64(10), @@ -777,7 +709,7 @@ func Test_ListProjectItems(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]interface{}{ "owner": "octo-org", "project_number": float64(10), @@ -786,7 +718,7 @@ func Test_ListProjectItems(t *testing.T) { }, { name: "missing project_number", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -877,12 +809,9 @@ func Test_GetProjectItem(t *testing.T) { }{ { name: "success organization item", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items/{item_id}", Method: http.MethodGet}, - mockResponse(t, http.StatusOK, orgItem), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ItemsByItemID: mockResponse(t, http.StatusOK, orgItem), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -893,21 +822,17 @@ func Test_GetProjectItem(t *testing.T) { }, { name: "success organization item with fields", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items/{item_id}", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - q := r.URL.Query() - if q.Get("fields") == "123,456" { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(orgItem)) - return - } - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message":"unexpected query params"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ItemsByItemID: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + if q.Get("fields") == "123,456" { + mockResponse(t, http.StatusOK, orgItem)(w, r) + return + } + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message":"unexpected query params"}`)) + }), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -919,12 +844,9 @@ func Test_GetProjectItem(t *testing.T) { }, { name: "success user item", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{user}/projectsV2/{project}/items/{item_id}", Method: http.MethodGet}, - mockResponse(t, http.StatusOK, userItem), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUsersProjectsV2ItemsByItemID: mockResponse(t, http.StatusOK, userItem), + }), requestArgs: map[string]any{ "owner": "octocat", "owner_type": "user", @@ -935,12 +857,9 @@ func Test_GetProjectItem(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items/{item_id}", Method: http.MethodGet}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ItemsByItemID: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -952,7 +871,7 @@ func Test_GetProjectItem(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner_type": "org", "project_number": float64(10), @@ -962,7 +881,7 @@ func Test_GetProjectItem(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "project_number": float64(10), @@ -972,7 +891,7 @@ func Test_GetProjectItem(t *testing.T) { }, { name: "missing project_number", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -982,7 +901,7 @@ func Test_GetProjectItem(t *testing.T) { }, { name: "missing item_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1086,24 +1005,20 @@ func Test_AddProjectItem(t *testing.T) { }{ { name: "success organization issue", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items", Method: http.MethodPost}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) - assert.NoError(t, err) - var payload struct { - Type string `json:"type"` - ID int `json:"id"` - } - assert.NoError(t, json.Unmarshal(body, &payload)) - assert.Equal(t, "Issue", payload.Type) - assert.Equal(t, 9876, payload.ID) - w.WriteHeader(http.StatusCreated) - _, _ = w.Write(mock.MustMarshal(orgItem)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostOrgsProjectsV2Items: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + assert.NoError(t, err) + var payload struct { + Type string `json:"type"` + ID int `json:"id"` + } + assert.NoError(t, json.Unmarshal(body, &payload)) + assert.Equal(t, "Issue", payload.Type) + assert.Equal(t, 9876, payload.ID) + mockResponse(t, http.StatusCreated, orgItem)(w, r) + }), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1117,24 +1032,20 @@ func Test_AddProjectItem(t *testing.T) { }, { name: "success user pull request", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{user}/projectsV2/{project}/items", Method: http.MethodPost}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) - assert.NoError(t, err) - var payload struct { - Type string `json:"type"` - ID int `json:"id"` - } - assert.NoError(t, json.Unmarshal(body, &payload)) - assert.Equal(t, "PullRequest", payload.Type) - assert.Equal(t, 7654, payload.ID) - w.WriteHeader(http.StatusCreated) - _, _ = w.Write(mock.MustMarshal(userItem)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostUsersProjectsV2Items: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + assert.NoError(t, err) + var payload struct { + Type string `json:"type"` + ID int `json:"id"` + } + assert.NoError(t, json.Unmarshal(body, &payload)) + assert.Equal(t, "PullRequest", payload.Type) + assert.Equal(t, 7654, payload.ID) + mockResponse(t, http.StatusCreated, userItem)(w, r) + }), + }), requestArgs: map[string]any{ "owner": "octocat", "owner_type": "user", @@ -1148,12 +1059,9 @@ func Test_AddProjectItem(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items", Method: http.MethodPost}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostOrgsProjectsV2Items: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1166,7 +1074,7 @@ func Test_AddProjectItem(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner_type": "org", "project_number": float64(1), @@ -1177,7 +1085,7 @@ func Test_AddProjectItem(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "project_number": float64(1), @@ -1188,7 +1096,7 @@ func Test_AddProjectItem(t *testing.T) { }, { name: "missing project_number", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1199,7 +1107,7 @@ func Test_AddProjectItem(t *testing.T) { }, { name: "missing item_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1210,7 +1118,7 @@ func Test_AddProjectItem(t *testing.T) { }, { name: "missing item_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1310,27 +1218,23 @@ func Test_UpdateProjectItem(t *testing.T) { }{ { name: "success organization update", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items/{item_id}", Method: http.MethodPatch}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) - assert.NoError(t, err) - var payload struct { - Fields []struct { - ID int `json:"id"` - Value interface{} `json:"value"` - } `json:"fields"` - } - assert.NoError(t, json.Unmarshal(body, &payload)) - require.Len(t, payload.Fields, 1) - assert.Equal(t, 101, payload.Fields[0].ID) - assert.Equal(t, "Done", payload.Fields[0].Value) - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(orgUpdatedItem)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchOrgsProjectsV2ItemsByItemID: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + assert.NoError(t, err) + var payload struct { + Fields []struct { + ID int `json:"id"` + Value interface{} `json:"value"` + } `json:"fields"` + } + assert.NoError(t, json.Unmarshal(body, &payload)) + require.Len(t, payload.Fields, 1) + assert.Equal(t, 101, payload.Fields[0].ID) + assert.Equal(t, "Done", payload.Fields[0].Value) + mockResponse(t, http.StatusOK, orgUpdatedItem)(w, r) + }), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1345,27 +1249,23 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "success user update", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{user}/projectsV2/{project}/items/{item_id}", Method: http.MethodPatch}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) - assert.NoError(t, err) - var payload struct { - Fields []struct { - ID int `json:"id"` - Value interface{} `json:"value"` - } `json:"fields"` - } - assert.NoError(t, json.Unmarshal(body, &payload)) - require.Len(t, payload.Fields, 1) - assert.Equal(t, 202, payload.Fields[0].ID) - assert.Equal(t, 42.0, payload.Fields[0].Value) - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(userUpdatedItem)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchUsersProjectsV2ItemsByItemID: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + assert.NoError(t, err) + var payload struct { + Fields []struct { + ID int `json:"id"` + Value interface{} `json:"value"` + } `json:"fields"` + } + assert.NoError(t, json.Unmarshal(body, &payload)) + require.Len(t, payload.Fields, 1) + assert.Equal(t, 202, payload.Fields[0].ID) + assert.Equal(t, 42.0, payload.Fields[0].Value) + mockResponse(t, http.StatusOK, userUpdatedItem)(w, r) + }), + }), requestArgs: map[string]any{ "owner": "octocat", "owner_type": "user", @@ -1380,12 +1280,9 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items/{item_id}", Method: http.MethodPatch}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchOrgsProjectsV2ItemsByItemID: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1401,7 +1298,7 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner_type": "org", "project_number": float64(1), @@ -1415,7 +1312,7 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "project_number": float64(1), @@ -1429,7 +1326,7 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "missing project_number", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1443,7 +1340,7 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "missing item_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1457,7 +1354,7 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "missing updated_field", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1468,7 +1365,7 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "updated_field not object", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1480,7 +1377,7 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "updated_field missing id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1492,7 +1389,7 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "updated_field missing value", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1580,14 +1477,11 @@ func Test_DeleteProjectItem(t *testing.T) { }{ { name: "success organization delete", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items/{item_id}", Method: http.MethodDelete}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNoContent) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteOrgsProjectsV2ItemsByItemID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1598,14 +1492,11 @@ func Test_DeleteProjectItem(t *testing.T) { }, { name: "success user delete", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{user}/projectsV2/{project}/items/{item_id}", Method: http.MethodDelete}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNoContent) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteUsersProjectsV2ItemsByItemID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }), + }), requestArgs: map[string]any{ "owner": "octocat", "owner_type": "user", @@ -1616,12 +1507,9 @@ func Test_DeleteProjectItem(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items/{item_id}", Method: http.MethodDelete}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteOrgsProjectsV2ItemsByItemID: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1633,7 +1521,7 @@ func Test_DeleteProjectItem(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner_type": "org", "project_number": float64(1), @@ -1643,7 +1531,7 @@ func Test_DeleteProjectItem(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "project_number": float64(1), @@ -1653,7 +1541,7 @@ func Test_DeleteProjectItem(t *testing.T) { }, { name: "missing project_number", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1663,7 +1551,7 @@ func Test_DeleteProjectItem(t *testing.T) { }, { name: "missing item_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(nil), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", diff --git a/pkg/github/pullrequests_test.go b/pkg/github/pullrequests_test.go index 3cb41515d..d3f3f8dec 100644 --- a/pkg/github/pullrequests_test.go +++ b/pkg/github/pullrequests_test.go @@ -14,8 +14,6 @@ import ( "github.com/google/go-github/v79/github" "github.com/google/jsonschema-go/jsonschema" "github.com/shurcooL/githubv4" - - "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index 9d7501f35..f97effbbd 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -15,7 +15,6 @@ import ( "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" "github.com/google/jsonschema-go/jsonschema" - "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -72,36 +71,27 @@ func Test_GetFileContents(t *testing.T) { }{ { name: "successful text content fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - fileContent := &github.RepositoryContent{ - Name: github.Ptr("README.md"), - Path: github.Ptr("README.md"), - SHA: github.Ptr("abc123"), - Type: github.Ptr("file"), - } - contentBytes, _ := json.Marshal(fileContent) - _, _ = w.Write(contentBytes) - }), - ), - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByBranchByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "text/markdown") - _, _ = w.Write(mockRawContent) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) + }), + GetReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + fileContent := &github.RepositoryContent{ + Name: github.Ptr("README.md"), + Path: github.Ptr("README.md"), + SHA: github.Ptr("abc123"), + Type: github.Ptr("file"), + } + contentBytes, _ := json.Marshal(fileContent) + _, _ = w.Write(contentBytes) + }), + GetRawReposContentsByOwnerByRepoByBranchByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/markdown") + _, _ = w.Write(mockRawContent) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -116,37 +106,28 @@ func Test_GetFileContents(t *testing.T) { }, }, { - name: "successful file blob content fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - fileContent := &github.RepositoryContent{ - Name: github.Ptr("test.png"), - Path: github.Ptr("test.png"), - SHA: github.Ptr("def456"), - Type: github.Ptr("file"), - } - contentBytes, _ := json.Marshal(fileContent) - _, _ = w.Write(contentBytes) - }), - ), - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByBranchByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "image/png") - _, _ = w.Write(mockRawContent) - }), - ), - ), + name: "successful binary content fetch", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) + }), + GetReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + fileContent := &github.RepositoryContent{ + Name: github.Ptr("test.png"), + Path: github.Ptr("test.png"), + SHA: github.Ptr("img123"), + Type: github.Ptr("file"), + } + contentBytes, _ := json.Marshal(fileContent) + _, _ = w.Write(contentBytes) + }), + GetRawReposContentsByOwnerByRepoByBranchByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "image/png") + _, _ = w.Write(mockRawContent) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -162,36 +143,27 @@ func Test_GetFileContents(t *testing.T) { }, { name: "successful PDF file content fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - fileContent := &github.RepositoryContent{ - Name: github.Ptr("document.pdf"), - Path: github.Ptr("document.pdf"), - SHA: github.Ptr("pdf123"), - Type: github.Ptr("file"), - } - contentBytes, _ := json.Marshal(fileContent) - _, _ = w.Write(contentBytes) - }), - ), - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByBranchByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "application/pdf") - _, _ = w.Write(mockRawContent) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) + }), + GetReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + fileContent := &github.RepositoryContent{ + Name: github.Ptr("document.pdf"), + Path: github.Ptr("document.pdf"), + SHA: github.Ptr("pdf123"), + Type: github.Ptr("file"), + } + contentBytes, _ := json.Marshal(fileContent) + _, _ = w.Write(contentBytes) + }), + GetRawReposContentsByOwnerByRepoByBranchByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/pdf") + _, _ = w.Write(mockRawContent) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -207,76 +179,54 @@ func Test_GetFileContents(t *testing.T) { }, { name: "successful directory content fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"name": "repo", "default_branch": "main"}`)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposContentsByOwnerByRepoByPath, - expectQueryParams(t, map[string]string{}).andThen( - mockResponse(t, http.StatusOK, mockDirContent), - ), - ), - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByPath, - expectQueryParams(t, map[string]string{ - "branch": "main", - }).andThen( - mockResponse(t, http.StatusNotFound, nil), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"name": "repo", "default_branch": "main"}`)) + }), + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) + }), + GetReposContentsByOwnerByRepoByPath: mockResponse(t, http.StatusOK, mockDirContent), + GetRawReposContentsByOwnerByRepoByPath: expectQueryParams(t, map[string]string{ + "branch": "main", + }).andThen( + mockResponse(t, http.StatusNotFound, nil), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", - "path": "src/", + "path": "src", + "ref": "refs/heads/main", }, expectError: false, expectedResult: mockDirContent, }, { - name: "successful text content fetch with leading slash in path", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - fileContent := &github.RepositoryContent{ - Name: github.Ptr("README.md"), - Path: github.Ptr("README.md"), - SHA: github.Ptr("abc123"), - Type: github.Ptr("file"), - } - contentBytes, _ := json.Marshal(fileContent) - _, _ = w.Write(contentBytes) - }), - ), - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByBranchByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "text/markdown") - _, _ = w.Write(mockRawContent) - }), - ), - ), + name: "successful text content fetch with leading slash path", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) + }), + GetReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + fileContent := &github.RepositoryContent{ + Name: github.Ptr("README.md"), + Path: github.Ptr("README.md"), + SHA: github.Ptr("abc123"), + Type: github.Ptr("file"), + } + contentBytes, _ := json.Marshal(fileContent) + _, _ = w.Write(contentBytes) + }), + GetRawReposContentsByOwnerByRepoByBranchByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/markdown") + _, _ = w.Write(mockRawContent) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -292,29 +242,20 @@ func Test_GetFileContents(t *testing.T) { }, { name: "content fetch fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) + }), + GetReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + GetRawReposContentsByOwnerByRepoByBranchByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -418,12 +359,9 @@ func Test_ForkRepository(t *testing.T) { }{ { name: "successful repository fork", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposForksByOwnerByRepo, - mockResponse(t, http.StatusAccepted, mockForkedRepo), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposForksByOwnerByRepo: mockResponse(t, http.StatusAccepted, mockForkedRepo), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -433,15 +371,12 @@ func Test_ForkRepository(t *testing.T) { }, { name: "repository fork fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposForksByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusForbidden) - _, _ = w.Write([]byte(`{"message": "Forbidden"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposForksByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusForbidden) + _, _ = w.Write([]byte(`{"message": "Forbidden"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -534,16 +469,10 @@ func Test_CreateBranch(t *testing.T) { }{ { name: "successful branch creation with from_branch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, - mockSourceRef, - ), - mock.WithRequestMatch( - mock.PostReposGitRefsByOwnerByRepo, - mockCreatedRef, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockSourceRef), + PostReposGitRefsByOwnerByRepo: mockResponse(t, http.StatusCreated, mockCreatedRef), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -555,25 +484,16 @@ func Test_CreateBranch(t *testing.T) { }, { name: "successful branch creation with default branch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposByOwnerByRepo, - mockRepo, - ), - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, - mockSourceRef, - ), - mock.WithRequestMatchHandler( - mock.PostReposGitRefsByOwnerByRepo, - expectRequestBody(t, map[string]interface{}{ - "ref": "refs/heads/new-feature", - "sha": "abc123def456", - }).andThen( - mockResponse(t, http.StatusCreated, mockCreatedRef), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, mockRepo), + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockSourceRef), + PostReposGitRefsByOwnerByRepo: expectRequestBody(t, map[string]interface{}{ + "ref": "refs/heads/new-feature", + "sha": "abc123def456", + }).andThen( + mockResponse(t, http.StatusCreated, mockCreatedRef), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -584,15 +504,12 @@ func Test_CreateBranch(t *testing.T) { }, { name: "fail to get repository", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Repository not found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Repository not found"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "nonexistent-repo", @@ -603,15 +520,12 @@ func Test_CreateBranch(t *testing.T) { }, { name: "fail to get reference", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Reference not found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Reference not found"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -623,19 +537,13 @@ func Test_CreateBranch(t *testing.T) { }, { name: "fail to create branch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, - mockSourceRef, - ), - mock.WithRequestMatchHandler( - mock.PostReposGitRefsByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnprocessableEntity) - _, _ = w.Write([]byte(`{"message": "Reference already exists"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockSourceRef), + PostReposGitRefsByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"message": "Reference already exists"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -744,12 +652,9 @@ func Test_GetCommit(t *testing.T) { }{ { name: "successful commit fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposCommitsByOwnerByRepoByRef, - mockResponse(t, http.StatusOK, mockCommit), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposCommitsByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockCommit), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -760,15 +665,12 @@ func Test_GetCommit(t *testing.T) { }, { name: "commit fetch fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposCommitsByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposCommitsByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -926,12 +828,9 @@ func Test_ListCommits(t *testing.T) { }{ { name: "successful commits fetch with default params", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposCommitsByOwnerByRepo, - mockCommits, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposCommitsByOwnerByRepo: mockResponse(t, http.StatusOK, mockCommits), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -941,19 +840,16 @@ func Test_ListCommits(t *testing.T) { }, { name: "successful commits fetch with branch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposCommitsByOwnerByRepo, - expectQueryParams(t, map[string]string{ - "author": "username", - "sha": "main", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockCommits), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposCommitsByOwnerByRepo: expectQueryParams(t, map[string]string{ + "author": "username", + "sha": "main", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockCommits), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -965,17 +861,14 @@ func Test_ListCommits(t *testing.T) { }, { name: "successful commits fetch with pagination", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposCommitsByOwnerByRepo, - expectQueryParams(t, map[string]string{ - "page": "2", - "per_page": "10", - }).andThen( - mockResponse(t, http.StatusOK, mockCommits), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposCommitsByOwnerByRepo: expectQueryParams(t, map[string]string{ + "page": "2", + "per_page": "10", + }).andThen( + mockResponse(t, http.StatusOK, mockCommits), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -987,15 +880,12 @@ func Test_ListCommits(t *testing.T) { }, { name: "commits fetch fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposCommitsByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposCommitsByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "nonexistent-repo", @@ -1110,18 +1000,15 @@ func Test_CreateOrUpdateFile(t *testing.T) { }{ { name: "successful file creation", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, - expectRequestBody(t, map[string]interface{}{ - "message": "Add example file", - "content": "IyBFeGFtcGxlCgpUaGlzIGlzIGFuIGV4YW1wbGUgZmlsZS4=", // Base64 encoded content - "branch": "main", - }).andThen( - mockResponse(t, http.StatusOK, mockFileResponse), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]interface{}{ + "message": "Add example file", + "content": "IyBFeGFtcGxlCgpUaGlzIGlzIGFuIGV4YW1wbGUgZmlsZS4=", // Base64 encoded content + "branch": "main", + }).andThen( + mockResponse(t, http.StatusOK, mockFileResponse), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1135,19 +1022,16 @@ func Test_CreateOrUpdateFile(t *testing.T) { }, { name: "successful file update with SHA", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, - expectRequestBody(t, map[string]interface{}{ - "message": "Update example file", - "content": "IyBVcGRhdGVkIEV4YW1wbGUKClRoaXMgZmlsZSBoYXMgYmVlbiB1cGRhdGVkLg==", // Base64 encoded content - "branch": "main", - "sha": "abc123def456", - }).andThen( - mockResponse(t, http.StatusOK, mockFileResponse), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]interface{}{ + "message": "Update example file", + "content": "IyBVcGRhdGVkIEV4YW1wbGUKClRoaXMgZmlsZSBoYXMgYmVlbiB1cGRhdGVkLg==", // Base64 encoded content + "branch": "main", + "sha": "abc123def456", + }).andThen( + mockResponse(t, http.StatusOK, mockFileResponse), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1162,15 +1046,12 @@ func Test_CreateOrUpdateFile(t *testing.T) { }, { name: "file creation fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnprocessableEntity) - _, _ = w.Write([]byte(`{"message": "Invalid request"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"message": "Invalid request"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1184,35 +1065,26 @@ func Test_CreateOrUpdateFile(t *testing.T) { }, { name: "sha validation - current sha matches (304 Not Modified)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/contents/docs/example.md", - Method: "HEAD", - }, - http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - // Verify If-None-Match header is set correctly - ifNoneMatch := req.Header.Get("If-None-Match") - if ifNoneMatch == `"abc123def456"` { - w.WriteHeader(http.StatusNotModified) - } else { - w.WriteHeader(http.StatusOK) - w.Header().Set("ETag", `"abc123def456"`) - } - }), - ), - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, - expectRequestBody(t, map[string]interface{}{ - "message": "Update example file", - "content": "IyBVcGRhdGVkIEV4YW1wbGUKClRoaXMgZmlsZSBoYXMgYmVlbiB1cGRhdGVkLg==", - "branch": "main", - "sha": "abc123def456", - }).andThen( - mockResponse(t, http.StatusOK, mockFileResponse), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "HEAD /repos/{owner}/{repo}/contents/{path:.*}": http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // Verify If-None-Match header is set correctly + ifNoneMatch := req.Header.Get("If-None-Match") + if ifNoneMatch == `"abc123def456"` { + w.WriteHeader(http.StatusNotModified) + return + } + w.Header().Set("ETag", `"abc123def456"`) + w.WriteHeader(http.StatusOK) + }), + PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]interface{}{ + "message": "Update example file", + "content": "IyBVcGRhdGVkIEV4YW1wbGUKClRoaXMgZmlsZSBoYXMgYmVlbiB1cGRhdGVkLg==", + "branch": "main", + "sha": "abc123def456", + }).andThen( + mockResponse(t, http.StatusOK, mockFileResponse), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1227,19 +1099,13 @@ func Test_CreateOrUpdateFile(t *testing.T) { }, { name: "sha validation - stale sha detected (200 OK with different ETag)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/contents/docs/example.md", - Method: "HEAD", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - // SHA doesn't match - return 200 with current ETag - w.Header().Set("ETag", `"newsha999888"`) - w.WriteHeader(http.StatusOK) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "HEAD /repos/{owner}/{repo}/contents/{path:.*}": http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + // SHA doesn't match - return 200 with current ETag + w.Header().Set("ETag", `"newsha999888"`) + w.WriteHeader(http.StatusOK) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1254,28 +1120,19 @@ func Test_CreateOrUpdateFile(t *testing.T) { }, { name: "sha validation - file doesn't exist (404), proceed with create", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/contents/docs/example.md", - Method: "HEAD", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - }), - ), - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, - expectRequestBody(t, map[string]interface{}{ - "message": "Create new file", - "content": "IyBOZXcgRmlsZQoKVGhpcyBpcyBhIG5ldyBmaWxlLg==", - "branch": "main", - "sha": "ignoredsha", // SHA is sent but GitHub API ignores it for new files - }).andThen( - mockResponse(t, http.StatusCreated, mockFileResponse), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "HEAD /repos/{owner}/{repo}/contents/{path:.*}": http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + }), + PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]interface{}{ + "message": "Create new file", + "content": "IyBOZXcgRmlsZQoKVGhpcyBpcyBhIG5ldyBmaWxlLg==", + "branch": "main", + "sha": "ignoredsha", // SHA is sent but GitHub API ignores it for new files + }).andThen( + mockResponse(t, http.StatusCreated, mockFileResponse), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1290,29 +1147,20 @@ func Test_CreateOrUpdateFile(t *testing.T) { }, { name: "no sha provided - file exists, returns warning", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/contents/docs/example.md", - Method: "HEAD", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("ETag", `"existing123"`) - w.WriteHeader(http.StatusOK) - }), - ), - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, - expectRequestBody(t, map[string]interface{}{ - "message": "Update without SHA", - "content": "IyBVcGRhdGVkCgpVcGRhdGVkIHdpdGhvdXQgU0hBLg==", - "branch": "main", - "sha": "existing123", // SHA is automatically added from ETag - }).andThen( - mockResponse(t, http.StatusOK, mockFileResponse), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "HEAD /repos/{owner}/{repo}/contents/{path:.*}": http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("ETag", `"existing123"`) + w.WriteHeader(http.StatusOK) + }), + PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]interface{}{ + "message": "Update without SHA", + "content": "IyBVcGRhdGVkCgpVcGRhdGVkIHdpdGhvdXQgU0hBLg==", + "branch": "main", + "sha": "existing123", // SHA is automatically added from ETag + }).andThen( + mockResponse(t, http.StatusOK, mockFileResponse), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1326,27 +1174,18 @@ func Test_CreateOrUpdateFile(t *testing.T) { }, { name: "no sha provided - file doesn't exist, no warning", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/contents/docs/example.md", - Method: "HEAD", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - }), - ), - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, - expectRequestBody(t, map[string]interface{}{ - "message": "Create new file", - "content": "IyBOZXcgRmlsZQoKQ3JlYXRlZCB3aXRob3V0IFNIQQ==", - "branch": "main", - }).andThen( - mockResponse(t, http.StatusCreated, mockFileResponse), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "HEAD /repos/{owner}/{repo}/contents/{path:.*}": http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + }), + PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]interface{}{ + "message": "Create new file", + "content": "IyBOZXcgRmlsZQoKQ3JlYXRlZCB3aXRob3V0IFNIQQ==", + "branch": "main", + }).andThen( + mockResponse(t, http.StatusCreated, mockFileResponse), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1453,22 +1292,16 @@ func Test_CreateRepository(t *testing.T) { }{ { name: "successful repository creation with all parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/user/repos", - Method: "POST", - }, - expectRequestBody(t, map[string]interface{}{ - "name": "test-repo", - "description": "Test repository", - "private": true, - "auto_init": true, - }).andThen( - mockResponse(t, http.StatusCreated, mockRepo), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "POST /user/repos": expectRequestBody(t, map[string]interface{}{ + "name": "test-repo", + "description": "Test repository", + "private": true, + "auto_init": true, + }).andThen( + mockResponse(t, http.StatusCreated, mockRepo), + ), + }), requestArgs: map[string]interface{}{ "name": "test-repo", "description": "Test repository", @@ -1480,22 +1313,16 @@ func Test_CreateRepository(t *testing.T) { }, { name: "successful repository creation in organization", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/orgs/testorg/repos", - Method: "POST", - }, - expectRequestBody(t, map[string]interface{}{ - "name": "test-repo", - "description": "Test repository", - "private": false, - "auto_init": true, - }).andThen( - mockResponse(t, http.StatusCreated, mockRepo), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "POST /orgs/{org}/repos": expectRequestBody(t, map[string]interface{}{ + "name": "test-repo", + "description": "Test repository", + "private": false, + "auto_init": true, + }).andThen( + mockResponse(t, http.StatusCreated, mockRepo), + ), + }), requestArgs: map[string]interface{}{ "name": "test-repo", "description": "Test repository", @@ -1508,22 +1335,16 @@ func Test_CreateRepository(t *testing.T) { }, { name: "successful repository creation with minimal parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/user/repos", - Method: "POST", - }, - expectRequestBody(t, map[string]interface{}{ - "name": "test-repo", - "auto_init": false, - "description": "", - "private": false, - }).andThen( - mockResponse(t, http.StatusCreated, mockRepo), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "POST /user/repos": expectRequestBody(t, map[string]interface{}{ + "name": "test-repo", + "auto_init": false, + "description": "", + "private": false, + }).andThen( + mockResponse(t, http.StatusCreated, mockRepo), + ), + }), requestArgs: map[string]interface{}{ "name": "test-repo", }, @@ -1532,18 +1353,12 @@ func Test_CreateRepository(t *testing.T) { }, { name: "repository creation fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/user/repos", - Method: "POST", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnprocessableEntity) - _, _ = w.Write([]byte(`{"message": "Repository creation failed"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "POST /user/repos": http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"message": "Repository creation failed"}`)) + }), + }), requestArgs: map[string]interface{}{ "name": "invalid-repo", }, @@ -1656,62 +1471,42 @@ func Test_PushFiles(t *testing.T) { }{ { name: "successful push of multiple files", - mockedClient: mock.NewMockedHTTPClient( - // Get branch reference - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, - mockRef, - ), - // Get commit - mock.WithRequestMatch( - mock.GetReposGitCommitsByOwnerByRepoByCommitSha, - mockCommit, - ), - // Create tree - mock.WithRequestMatchHandler( - mock.PostReposGitTreesByOwnerByRepo, - expectRequestBody(t, map[string]interface{}{ - "base_tree": "def456", - "tree": []interface{}{ - map[string]interface{}{ - "path": "README.md", - "mode": "100644", - "type": "blob", - "content": "# Updated README\n\nThis is an updated README file.", - }, - map[string]interface{}{ - "path": "docs/example.md", - "mode": "100644", - "type": "blob", - "content": "# Example\n\nThis is an example file.", - }, + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockRef), + GetReposGitCommitsByOwnerByRepoByCommitSHA: mockResponse(t, http.StatusOK, mockCommit), + PostReposGitTreesByOwnerByRepo: expectRequestBody(t, map[string]interface{}{ + "base_tree": "def456", + "tree": []interface{}{ + map[string]interface{}{ + "path": "README.md", + "mode": "100644", + "type": "blob", + "content": "# Updated README\n\nThis is an updated README file.", }, - }).andThen( - mockResponse(t, http.StatusCreated, mockTree), - ), - ), - // Create commit - mock.WithRequestMatchHandler( - mock.PostReposGitCommitsByOwnerByRepo, - expectRequestBody(t, map[string]interface{}{ - "message": "Update multiple files", - "tree": "ghi789", - "parents": []interface{}{"abc123"}, - }).andThen( - mockResponse(t, http.StatusCreated, mockNewCommit), - ), - ), - // Update reference - mock.WithRequestMatchHandler( - mock.PatchReposGitRefsByOwnerByRepoByRef, - expectRequestBody(t, map[string]interface{}{ - "sha": "jkl012", - "force": false, - }).andThen( - mockResponse(t, http.StatusOK, mockUpdatedRef), - ), - ), - ), + map[string]interface{}{ + "path": "docs/example.md", + "mode": "100644", + "type": "blob", + "content": "# Example\n\nThis is an example file.", + }, + }, + }).andThen( + mockResponse(t, http.StatusCreated, mockTree), + ), + PostReposGitCommitsByOwnerByRepo: expectRequestBody(t, map[string]interface{}{ + "message": "Update multiple files", + "tree": "ghi789", + "parents": []interface{}{"abc123"}, + }).andThen( + mockResponse(t, http.StatusCreated, mockNewCommit), + ), + PatchReposGitRefsByOwnerByRepoByRef: expectRequestBody(t, map[string]interface{}{ + "sha": "jkl012", + "force": false, + }).andThen( + mockResponse(t, http.StatusOK, mockUpdatedRef), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1733,9 +1528,7 @@ func Test_PushFiles(t *testing.T) { }, { name: "fails when files parameter is invalid", - mockedClient: mock.NewMockedHTTPClient( - // No requests expected - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1748,18 +1541,10 @@ func Test_PushFiles(t *testing.T) { }, { name: "fails when files contains object without path", - mockedClient: mock.NewMockedHTTPClient( - // Get branch reference - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, - mockRef, - ), - // Get commit - mock.WithRequestMatch( - mock.GetReposGitCommitsByOwnerByRepoByCommitSha, - mockCommit, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockRef), + GetReposGitCommitsByOwnerByRepoByCommitSHA: mockResponse(t, http.StatusOK, mockCommit), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1776,18 +1561,10 @@ func Test_PushFiles(t *testing.T) { }, { name: "fails when files contains object without content", - mockedClient: mock.NewMockedHTTPClient( - // Get branch reference - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, - mockRef, - ), - // Get commit - mock.WithRequestMatch( - mock.GetReposGitCommitsByOwnerByRepoByCommitSha, - mockCommit, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockRef), + GetReposGitCommitsByOwnerByRepoByCommitSHA: mockResponse(t, http.StatusOK, mockCommit), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1805,12 +1582,9 @@ func Test_PushFiles(t *testing.T) { }, { name: "fails to get branch reference", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - mockResponse(t, http.StatusNotFound, nil), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusNotFound, nil), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1828,18 +1602,10 @@ func Test_PushFiles(t *testing.T) { }, { name: "fails to get base commit", - mockedClient: mock.NewMockedHTTPClient( - // Get branch reference - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, - mockRef, - ), - // Fail to get commit - mock.WithRequestMatchHandler( - mock.GetReposGitCommitsByOwnerByRepoByCommitSha, - mockResponse(t, http.StatusNotFound, nil), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockRef), + GetReposGitCommitsByOwnerByRepoByCommitSHA: mockResponse(t, http.StatusNotFound, nil), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1857,23 +1623,11 @@ func Test_PushFiles(t *testing.T) { }, { name: "fails to create tree", - mockedClient: mock.NewMockedHTTPClient( - // Get branch reference - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, - mockRef, - ), - // Get commit - mock.WithRequestMatch( - mock.GetReposGitCommitsByOwnerByRepoByCommitSha, - mockCommit, - ), - // Fail to create tree - mock.WithRequestMatchHandler( - mock.PostReposGitTreesByOwnerByRepo, - mockResponse(t, http.StatusInternalServerError, nil), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockRef), + GetReposGitCommitsByOwnerByRepoByCommitSHA: mockResponse(t, http.StatusOK, mockCommit), + PostReposGitTreesByOwnerByRepo: mockResponse(t, http.StatusInternalServerError, nil), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1971,11 +1725,11 @@ func Test_ListBranches(t *testing.T) { // Test cases tests := []struct { - name string - args map[string]interface{} - mockResponses []mock.MockBackendOption - wantErr bool - errContains string + name string + args map[string]interface{} + mockedClient *http.Client + wantErr bool + errContains string }{ { name: "success", @@ -1984,12 +1738,9 @@ func Test_ListBranches(t *testing.T) { "repo": "repo", "page": float64(2), }, - mockResponses: []mock.MockBackendOption{ - mock.WithRequestMatch( - mock.GetReposBranchesByOwnerByRepo, - mockBranches, - ), - }, + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposBranchesByOwnerByRepo: mockResponse(t, http.StatusOK, mockBranches), + }), wantErr: false, }, { @@ -1997,25 +1748,25 @@ func Test_ListBranches(t *testing.T) { args: map[string]interface{}{ "repo": "repo", }, - mockResponses: []mock.MockBackendOption{}, - wantErr: false, - errContains: "missing required parameter: owner", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), + wantErr: false, + errContains: "missing required parameter: owner", }, { name: "missing repo", args: map[string]interface{}{ "owner": "owner", }, - mockResponses: []mock.MockBackendOption{}, - wantErr: false, - errContains: "missing required parameter: repo", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), + wantErr: false, + errContains: "missing required parameter: repo", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create mock client - mockClient := github.NewClient(mock.NewMockedHTTPClient(tt.mockResponses...)) + mockClient := github.NewClient(tt.mockedClient) deps := BaseDeps{ Client: mockClient, } @@ -2112,61 +1863,41 @@ func Test_DeleteFile(t *testing.T) { }{ { name: "successful file deletion using Git Data API", - mockedClient: mock.NewMockedHTTPClient( - // Get branch reference - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, - mockRef, - ), - // Get commit - mock.WithRequestMatch( - mock.GetReposGitCommitsByOwnerByRepoByCommitSha, - mockCommit, - ), - // Create tree - mock.WithRequestMatchHandler( - mock.PostReposGitTreesByOwnerByRepo, - expectRequestBody(t, map[string]interface{}{ - "base_tree": "def456", - "tree": []interface{}{ - map[string]interface{}{ - "path": "docs/example.md", - "mode": "100644", - "type": "blob", - "sha": nil, - }, + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockRef), + GetReposGitCommitsByOwnerByRepoByCommitSHA: mockResponse(t, http.StatusOK, mockCommit), + PostReposGitTreesByOwnerByRepo: expectRequestBody(t, map[string]interface{}{ + "base_tree": "def456", + "tree": []interface{}{ + map[string]interface{}{ + "path": "docs/example.md", + "mode": "100644", + "type": "blob", + "sha": nil, }, - }).andThen( - mockResponse(t, http.StatusCreated, mockTree), - ), - ), - // Create commit - mock.WithRequestMatchHandler( - mock.PostReposGitCommitsByOwnerByRepo, - expectRequestBody(t, map[string]interface{}{ - "message": "Delete example file", - "tree": "ghi789", - "parents": []interface{}{"abc123"}, - }).andThen( - mockResponse(t, http.StatusCreated, mockNewCommit), - ), - ), - // Update reference - mock.WithRequestMatchHandler( - mock.PatchReposGitRefsByOwnerByRepoByRef, - expectRequestBody(t, map[string]interface{}{ - "sha": "jkl012", - "force": false, - }).andThen( - mockResponse(t, http.StatusOK, &github.Reference{ - Ref: github.Ptr("refs/heads/main"), - Object: &github.GitObject{ - SHA: github.Ptr("jkl012"), - }, - }), - ), + }, + }).andThen( + mockResponse(t, http.StatusCreated, mockTree), + ), + PostReposGitCommitsByOwnerByRepo: expectRequestBody(t, map[string]interface{}{ + "message": "Delete example file", + "tree": "ghi789", + "parents": []interface{}{"abc123"}, + }).andThen( + mockResponse(t, http.StatusCreated, mockNewCommit), + ), + PatchReposGitRefsByOwnerByRepoByRef: expectRequestBody(t, map[string]interface{}{ + "sha": "jkl012", + "force": false, + }).andThen( + mockResponse(t, http.StatusOK, &github.Reference{ + Ref: github.Ptr("refs/heads/main"), + Object: &github.GitObject{ + SHA: github.Ptr("jkl012"), + }, + }), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2179,15 +1910,12 @@ func Test_DeleteFile(t *testing.T) { }, { name: "file deletion fails - branch not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Reference not found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Reference not found"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2223,6 +1951,7 @@ func Test_DeleteFile(t *testing.T) { } require.NoError(t, err) + require.False(t, result.IsError, "unexpected tool error: %s", getTextResult(t, result).Text) // Parse the result and get the text content if no error textContent := getTextResult(t, result) @@ -2289,17 +2018,14 @@ func Test_ListTags(t *testing.T) { }{ { name: "successful tags list", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposTagsByOwnerByRepo, - expectPath( - t, - "/repos/owner/repo/tags", - ).andThen( - mockResponse(t, http.StatusOK, mockTags), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposTagsByOwnerByRepo: expectPath( + t, + "/repos/owner/repo/tags", + ).andThen( + mockResponse(t, http.StatusOK, mockTags), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2309,15 +2035,12 @@ func Test_ListTags(t *testing.T) { }, { name: "list tags fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposTagsByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(`{"message": "Internal Server Error"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposTagsByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(`{"message": "Internal Server Error"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2415,26 +2138,20 @@ func Test_GetTag(t *testing.T) { }{ { name: "successful tag retrieval", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - expectPath( - t, - "/repos/owner/repo/git/ref/tags/v1.0.0", - ).andThen( - mockResponse(t, http.StatusOK, mockTagRef), - ), - ), - mock.WithRequestMatchHandler( - mock.GetReposGitTagsByOwnerByRepoByTagSha, - expectPath( - t, - "/repos/owner/repo/git/tags/v1.0.0-tag-sha", - ).andThen( - mockResponse(t, http.StatusOK, mockTagObj), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: expectPath( + t, + "/repos/owner/repo/git/ref/tags/v1.0.0", + ).andThen( + mockResponse(t, http.StatusOK, mockTagRef), + ), + GetReposGitTagsByOwnerByRepoByTagSHA: expectPath( + t, + "/repos/owner/repo/git/tags/v1.0.0-tag-sha", + ).andThen( + mockResponse(t, http.StatusOK, mockTagObj), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2445,15 +2162,12 @@ func Test_GetTag(t *testing.T) { }, { name: "tag reference not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Reference does not exist"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Reference does not exist"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2464,19 +2178,13 @@ func Test_GetTag(t *testing.T) { }, { name: "tag object not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, - mockTagRef, - ), - mock.WithRequestMatchHandler( - mock.GetReposGitTagsByOwnerByRepoByTagSha, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Tag object does not exist"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockTagRef), + GetReposGitTagsByOwnerByRepoByTagSHA: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Tag object does not exist"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2568,12 +2276,9 @@ func Test_ListReleases(t *testing.T) { }{ { name: "successful releases list", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposReleasesByOwnerByRepo, - mockReleases, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposReleasesByOwnerByRepo: mockResponse(t, http.StatusOK, mockReleases), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2583,15 +2288,12 @@ func Test_ListReleases(t *testing.T) { }, { name: "releases list fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposReleasesByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposReleasesByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2659,12 +2361,9 @@ func Test_GetLatestRelease(t *testing.T) { }{ { name: "successful latest release fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposReleasesLatestByOwnerByRepo, - mockRelease, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposReleasesLatestByOwnerByRepo: mockResponse(t, http.StatusOK, mockRelease), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2674,15 +2373,12 @@ func Test_GetLatestRelease(t *testing.T) { }, { name: "latest release fetch fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposReleasesLatestByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposReleasesLatestByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2756,12 +2452,9 @@ func Test_GetReleaseByTag(t *testing.T) { }{ { name: "successful release by tag fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposReleasesTagsByOwnerByRepoByTag, - mockRelease, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposReleasesTagsByOwnerByRepoByTag: mockResponse(t, http.StatusOK, mockRelease), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2772,7 +2465,7 @@ func Test_GetReleaseByTag(t *testing.T) { }, { name: "missing owner parameter", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "repo": "repo", "tag": "v1.0.0", @@ -2782,7 +2475,7 @@ func Test_GetReleaseByTag(t *testing.T) { }, { name: "missing repo parameter", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "owner": "owner", "tag": "v1.0.0", @@ -2792,7 +2485,7 @@ func Test_GetReleaseByTag(t *testing.T) { }, { name: "missing tag parameter", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2802,15 +2495,12 @@ func Test_GetReleaseByTag(t *testing.T) { }, { name: "release by tag not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposReleasesTagsByOwnerByRepoByTag, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposReleasesTagsByOwnerByRepoByTag: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2821,15 +2511,12 @@ func Test_GetReleaseByTag(t *testing.T) { }, { name: "server error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposReleasesTagsByOwnerByRepoByTag, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(`{"message": "Internal Server Error"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposReleasesTagsByOwnerByRepoByTag: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(`{"message": "Internal Server Error"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -3004,7 +2691,7 @@ func Test_resolveGitReference(t *testing.T) { sha: "123sha456", mockSetup: func() *http.Client { // No API calls should be made when SHA is provided - return mock.NewMockedHTTPClient() + return MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}) }, expectedOutput: &raw.ContentOpts{ SHA: "123sha456", @@ -3016,23 +2703,17 @@ func Test_resolveGitReference(t *testing.T) { ref: "", sha: "", mockSetup: func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"name": "repo", "default_branch": "main"}`)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Contains(t, r.URL.Path, "/git/ref/heads/main") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": "main-sha"}}`)) - }), - ), - ) + return MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"name": "repo", "default_branch": "main"}`)) + }), + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Contains(t, r.URL.Path, "/git/ref/heads/main") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": "main-sha"}}`)) + }), + }) }, expectedOutput: &raw.ContentOpts{ Ref: "refs/heads/main", @@ -3045,16 +2726,13 @@ func Test_resolveGitReference(t *testing.T) { ref: "refs/heads/feature-branch", sha: "", mockSetup: func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Contains(t, r.URL.Path, "/git/ref/heads/feature-branch") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/feature-branch", "object": {"sha": "feature-sha"}}`)) - }), - ), - ) + return MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Contains(t, r.URL.Path, "/git/ref/heads/feature-branch") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/feature-branch", "object": {"sha": "feature-sha"}}`)) + }), + }) }, expectedOutput: &raw.ContentOpts{ Ref: "refs/heads/feature-branch", @@ -3067,20 +2745,17 @@ func Test_resolveGitReference(t *testing.T) { ref: "main", sha: "", mockSetup: func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.Contains(r.URL.Path, "/git/ref/heads/main") { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": "main-sha"}}`)) - } else { - t.Errorf("Unexpected path: %s", r.URL.Path) - w.WriteHeader(http.StatusNotFound) - } - }), - ), - ) + return MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.Contains(r.URL.Path, "/git/ref/heads/main") { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": "main-sha"}}`)) + return + } + t.Errorf("Unexpected path: %s", r.URL.Path) + w.WriteHeader(http.StatusNotFound) + }), + }) }, expectedOutput: &raw.ContentOpts{ Ref: "refs/heads/main", @@ -3093,24 +2768,21 @@ func Test_resolveGitReference(t *testing.T) { ref: "v1.0.0", sha: "", mockSetup: func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch { - case strings.Contains(r.URL.Path, "/git/ref/heads/v1.0.0"): - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - case strings.Contains(r.URL.Path, "/git/ref/tags/v1.0.0"): - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/tags/v1.0.0", "object": {"sha": "tag-sha"}}`)) - default: - t.Errorf("Unexpected path: %s", r.URL.Path) - w.WriteHeader(http.StatusNotFound) - } - }), - ), - ) + return MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.Contains(r.URL.Path, "/git/ref/heads/v1.0.0"): + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + case strings.Contains(r.URL.Path, "/git/ref/tags/v1.0.0"): + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/tags/v1.0.0", "object": {"sha": "tag-sha"}}`)) + default: + t.Errorf("Unexpected path: %s", r.URL.Path) + w.WriteHeader(http.StatusNotFound) + } + }), + }) }, expectedOutput: &raw.ContentOpts{ Ref: "refs/tags/v1.0.0", @@ -3123,16 +2795,13 @@ func Test_resolveGitReference(t *testing.T) { ref: "heads/feature-branch", sha: "", mockSetup: func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Contains(t, r.URL.Path, "/git/ref/heads/feature-branch") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/feature-branch", "object": {"sha": "feature-sha"}}`)) - }), - ), - ) + return MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Contains(t, r.URL.Path, "/git/ref/heads/feature-branch") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/feature-branch", "object": {"sha": "feature-sha"}}`)) + }), + }) }, expectedOutput: &raw.ContentOpts{ Ref: "refs/heads/feature-branch", @@ -3145,16 +2814,13 @@ func Test_resolveGitReference(t *testing.T) { ref: "tags/v1.0.0", sha: "", mockSetup: func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Contains(t, r.URL.Path, "/git/ref/tags/v1.0.0") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/tags/v1.0.0", "object": {"sha": "tag-sha"}}`)) - }), - ), - ) + return MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Contains(t, r.URL.Path, "/git/ref/tags/v1.0.0") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/tags/v1.0.0", "object": {"sha": "tag-sha"}}`)) + }), + }) }, expectedOutput: &raw.ContentOpts{ Ref: "refs/tags/v1.0.0", @@ -3167,16 +2833,13 @@ func Test_resolveGitReference(t *testing.T) { ref: "nonexistent", sha: "", mockSetup: func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - // Both branch and tag attempts should return 404 - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ) + return MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + // Both branch and tag attempts should return 404 + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }) }, expectError: true, errorContains: "could not resolve ref \"nonexistent\" as a branch or a tag", @@ -3186,16 +2849,13 @@ func Test_resolveGitReference(t *testing.T) { ref: "refs/pull/123/head", sha: "", mockSetup: func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Contains(t, r.URL.Path, "/git/ref/pull/123/head") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/pull/123/head", "object": {"sha": "pr-sha"}}`)) - }), - ), - ) + return MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Contains(t, r.URL.Path, "/git/ref/pull/123/head") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/pull/123/head", "object": {"sha": "pr-sha"}}`)) + }), + }) }, expectedOutput: &raw.ContentOpts{ Ref: "refs/pull/123/head", @@ -3304,30 +2964,18 @@ func Test_ListStarredRepositories(t *testing.T) { }{ { name: "successful list for authenticated user", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetUserStarred, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(mockStarredRepos)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUserStarred: mockResponse(t, http.StatusOK, mockStarredRepos), + }), requestArgs: map[string]interface{}{}, expectError: false, expectedCount: 2, }, { name: "successful list for specific user", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetUsersStarredByUsername, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(mockStarredRepos)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUsersStarredByUsername: mockResponse(t, http.StatusOK, mockStarredRepos), + }), requestArgs: map[string]interface{}{ "username": "testuser", }, @@ -3336,15 +2984,12 @@ func Test_ListStarredRepositories(t *testing.T) { }, { name: "list fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetUserStarred, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUserStarred: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), requestArgs: map[string]interface{}{}, expectError: true, expectedErrMsg: "failed to list starred repositories", @@ -3418,14 +3063,11 @@ func Test_StarRepository(t *testing.T) { }{ { name: "successful star", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PutUserStarredByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNoContent) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutUserStarredByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }), + }), requestArgs: map[string]interface{}{ "owner": "testowner", "repo": "testrepo", @@ -3434,15 +3076,12 @@ func Test_StarRepository(t *testing.T) { }, { name: "star fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PutUserStarredByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutUserStarredByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "testowner", "repo": "nonexistent", @@ -3509,14 +3148,11 @@ func Test_UnstarRepository(t *testing.T) { }{ { name: "successful unstar", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.DeleteUserStarredByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNoContent) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteUserStarredByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }), + }), requestArgs: map[string]interface{}{ "owner": "testowner", "repo": "testrepo", @@ -3525,15 +3161,12 @@ func Test_UnstarRepository(t *testing.T) { }, { name: "unstar fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.DeleteUserStarredByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteUserStarredByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "testowner", "repo": "nonexistent", diff --git a/pkg/github/search_test.go b/pkg/github/search_test.go index be1b26714..e15758c3e 100644 --- a/pkg/github/search_test.go +++ b/pkg/github/search_test.go @@ -10,7 +10,6 @@ import ( "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v79/github" "github.com/google/jsonschema-go/jsonschema" - "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -67,20 +66,17 @@ func Test_SearchRepositories(t *testing.T) { }{ { name: "successful repository search", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchRepositories, - expectQueryParams(t, map[string]string{ - "q": "golang test", - "sort": "stars", - "order": "desc", - "page": "2", - "per_page": "10", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchRepositories: expectQueryParams(t, map[string]string{ + "q": "golang test", + "sort": "stars", + "order": "desc", + "page": "2", + "per_page": "10", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "golang test", "sort": "stars", @@ -93,18 +89,15 @@ func Test_SearchRepositories(t *testing.T) { }, { name: "repository search with default pagination", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchRepositories, - expectQueryParams(t, map[string]string{ - "q": "golang test", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchRepositories: expectQueryParams(t, map[string]string{ + "q": "golang test", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "golang test", }, @@ -113,15 +106,12 @@ func Test_SearchRepositories(t *testing.T) { }, { name: "search fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchRepositories, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message": "Invalid query"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchRepositories: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message": "Invalid query"}`)) + }), + }), requestArgs: map[string]interface{}{ "query": "invalid:query", }, @@ -194,18 +184,15 @@ func Test_SearchRepositories_FullOutput(t *testing.T) { }, } - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchRepositories, - expectQueryParams(t, map[string]string{ - "q": "golang test", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchRepositories: expectQueryParams(t, map[string]string{ + "q": "golang test", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ) + }) client := github.NewClient(mockedClient) serverTool := SearchRepositories(translations.NullTranslationHelper) @@ -291,20 +278,17 @@ func Test_SearchCode(t *testing.T) { }{ { name: "successful code search with all parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchCode, - expectQueryParams(t, map[string]string{ - "q": "fmt.Println language:go", - "sort": "indexed", - "order": "desc", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchCode: expectQueryParams(t, map[string]string{ + "q": "fmt.Println language:go", + "sort": "indexed", + "order": "desc", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "fmt.Println language:go", "sort": "indexed", @@ -317,18 +301,15 @@ func Test_SearchCode(t *testing.T) { }, { name: "code search with minimal parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchCode, - expectQueryParams(t, map[string]string{ - "q": "fmt.Println language:go", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchCode: expectQueryParams(t, map[string]string{ + "q": "fmt.Println language:go", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "fmt.Println language:go", }, @@ -337,15 +318,12 @@ func Test_SearchCode(t *testing.T) { }, { name: "search code fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchCode, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchCode: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) + }), + }), requestArgs: map[string]interface{}{ "query": "invalid:query", }, @@ -451,20 +429,17 @@ func Test_SearchUsers(t *testing.T) { }{ { name: "successful users search with all parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - expectQueryParams(t, map[string]string{ - "q": "type:user location:finland language:go", - "sort": "followers", - "order": "desc", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: expectQueryParams(t, map[string]string{ + "q": "type:user location:finland language:go", + "sort": "followers", + "order": "desc", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "location:finland language:go", "sort": "followers", @@ -477,18 +452,15 @@ func Test_SearchUsers(t *testing.T) { }, { name: "users search with minimal parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - expectQueryParams(t, map[string]string{ - "q": "type:user location:finland language:go", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: expectQueryParams(t, map[string]string{ + "q": "type:user location:finland language:go", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "location:finland language:go", }, @@ -497,18 +469,15 @@ func Test_SearchUsers(t *testing.T) { }, { name: "query with existing type:user filter - no duplication", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - expectQueryParams(t, map[string]string{ - "q": "type:user location:seattle followers:>100", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: expectQueryParams(t, map[string]string{ + "q": "type:user location:seattle followers:>100", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "type:user location:seattle followers:>100", }, @@ -517,18 +486,15 @@ func Test_SearchUsers(t *testing.T) { }, { name: "complex query with existing type:user filter and OR operators", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - expectQueryParams(t, map[string]string{ - "q": "type:user (location:seattle OR location:california) followers:>50", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: expectQueryParams(t, map[string]string{ + "q": "type:user (location:seattle OR location:california) followers:>50", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "type:user (location:seattle OR location:california) followers:>50", }, @@ -537,15 +503,12 @@ func Test_SearchUsers(t *testing.T) { }, { name: "search users fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) + }), + }), requestArgs: map[string]interface{}{ "query": "invalid:query", }, @@ -652,18 +615,15 @@ func Test_SearchOrgs(t *testing.T) { }{ { name: "successful org search", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - expectQueryParams(t, map[string]string{ - "q": "type:org github", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: expectQueryParams(t, map[string]string{ + "q": "type:org github", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "github", }, @@ -672,18 +632,15 @@ func Test_SearchOrgs(t *testing.T) { }, { name: "query with existing type:org filter - no duplication", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - expectQueryParams(t, map[string]string{ - "q": "type:org location:california followers:>1000", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: expectQueryParams(t, map[string]string{ + "q": "type:org location:california followers:>1000", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "type:org location:california followers:>1000", }, @@ -692,18 +649,15 @@ func Test_SearchOrgs(t *testing.T) { }, { name: "complex query with existing type:org filter and OR operators", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - expectQueryParams(t, map[string]string{ - "q": "type:org (location:seattle OR location:california OR location:newyork) repos:>10", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: expectQueryParams(t, map[string]string{ + "q": "type:org (location:seattle OR location:california OR location:newyork) repos:>10", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "type:org (location:seattle OR location:california OR location:newyork) repos:>10", }, @@ -712,15 +666,12 @@ func Test_SearchOrgs(t *testing.T) { }, { name: "org search fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) + }), + }), requestArgs: map[string]interface{}{ "query": "invalid:query", }, diff --git a/pkg/raw/raw_mock.go b/pkg/raw/raw_mock.go index 30c7759d3..f84c2e7c0 100644 --- a/pkg/raw/raw_mock.go +++ b/pkg/raw/raw_mock.go @@ -1,20 +1,8 @@ package raw -import "github.com/migueleliasweb/go-github-mock/src/mock" - -var GetRawReposContentsByOwnerByRepoByPath mock.EndpointPattern = mock.EndpointPattern{ - Pattern: "/{owner}/{repo}/HEAD/{path:.*}", - Method: "GET", -} -var GetRawReposContentsByOwnerByRepoByBranchByPath mock.EndpointPattern = mock.EndpointPattern{ - Pattern: "/{owner}/{repo}/refs/heads/{branch}/{path:.*}", - Method: "GET", -} -var GetRawReposContentsByOwnerByRepoByTagByPath mock.EndpointPattern = mock.EndpointPattern{ - Pattern: "/{owner}/{repo}/refs/tags/{tag}/{path:.*}", - Method: "GET", -} -var GetRawReposContentsByOwnerByRepoBySHAByPath mock.EndpointPattern = mock.EndpointPattern{ - Pattern: "/{owner}/{repo}/{sha}/{path:.*}", - Method: "GET", -} +const ( + GetRawReposContentsByOwnerByRepoByPath = "GET /{owner}/{repo}/HEAD/{path:.*}" + GetRawReposContentsByOwnerByRepoByBranchByPath = "GET /{owner}/{repo}/refs/heads/{branch}/{path:.*}" + GetRawReposContentsByOwnerByRepoByTagByPath = "GET /{owner}/{repo}/refs/tags/{tag}/{path:.*}" + GetRawReposContentsByOwnerByRepoBySHAByPath = "GET /{owner}/{repo}/{sha}/{path:.*}" +) diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index e44711943..c46b6f309 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -18,17 +18,14 @@ The following packages are included for the amd64, arm64 architectures. - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - - [github.com/google/go-github/v71/github](https://pkg.go.dev/github.com/google/go-github/v71/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v71.0.0/LICENSE)) - [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.3.0/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) - - [github.com/gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux) ([BSD-3-Clause](https://github.com/gorilla/mux/blob/v1.8.0/LICENSE)) - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) - [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md)) - [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE)) - [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md)) - - [github.com/migueleliasweb/go-github-mock/src/mock](https://pkg.go.dev/github.com/migueleliasweb/go-github-mock/src/mock) ([MIT](https://github.com/migueleliasweb/go-github-mock/blob/v1.3.0/LICENSE)) - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.2.0-pre.1/LICENSE)) - [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt)) - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE)) @@ -49,7 +46,6 @@ The following packages are included for the amd64, arm64 architectures. - [golang.org/x/net/html](https://pkg.go.dev/golang.org/x/net/html) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.38.0:LICENSE)) - [golang.org/x/sys/unix](https://pkg.go.dev/golang.org/x/sys/unix) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.31.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.28.0:LICENSE)) - - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.5.0:LICENSE)) - [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE)) [github/github-mcp-server]: https://github.com/github/github-mcp-server diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index f5c147d59..9bafbbdb5 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -18,17 +18,14 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - - [github.com/google/go-github/v71/github](https://pkg.go.dev/github.com/google/go-github/v71/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v71.0.0/LICENSE)) - [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.3.0/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) - - [github.com/gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux) ([BSD-3-Clause](https://github.com/gorilla/mux/blob/v1.8.0/LICENSE)) - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) - [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md)) - [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE)) - [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md)) - - [github.com/migueleliasweb/go-github-mock/src/mock](https://pkg.go.dev/github.com/migueleliasweb/go-github-mock/src/mock) ([MIT](https://github.com/migueleliasweb/go-github-mock/blob/v1.3.0/LICENSE)) - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.2.0-pre.1/LICENSE)) - [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt)) - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE)) @@ -49,7 +46,6 @@ The following packages are included for the 386, amd64, arm64 architectures. - [golang.org/x/net/html](https://pkg.go.dev/golang.org/x/net/html) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.38.0:LICENSE)) - [golang.org/x/sys/unix](https://pkg.go.dev/golang.org/x/sys/unix) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.31.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.28.0:LICENSE)) - - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.5.0:LICENSE)) - [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE)) [github/github-mcp-server]: https://github.com/github/github-mcp-server diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index b3de372e7..782e3fe2d 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -18,18 +18,15 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - - [github.com/google/go-github/v71/github](https://pkg.go.dev/github.com/google/go-github/v71/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v71.0.0/LICENSE)) - [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.3.0/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) - - [github.com/gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux) ([BSD-3-Clause](https://github.com/gorilla/mux/blob/v1.8.0/LICENSE)) - [github.com/inconshreveable/mousetrap](https://pkg.go.dev/github.com/inconshreveable/mousetrap) ([Apache-2.0](https://github.com/inconshreveable/mousetrap/blob/v1.1.0/LICENSE)) - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) - [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md)) - [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE)) - [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md)) - - [github.com/migueleliasweb/go-github-mock/src/mock](https://pkg.go.dev/github.com/migueleliasweb/go-github-mock/src/mock) ([MIT](https://github.com/migueleliasweb/go-github-mock/blob/v1.3.0/LICENSE)) - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.2.0-pre.1/LICENSE)) - [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt)) - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE)) @@ -50,7 +47,6 @@ The following packages are included for the 386, amd64, arm64 architectures. - [golang.org/x/net/html](https://pkg.go.dev/golang.org/x/net/html) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.38.0:LICENSE)) - [golang.org/x/sys/windows](https://pkg.go.dev/golang.org/x/sys/windows) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.31.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.28.0:LICENSE)) - - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.5.0:LICENSE)) - [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE)) [github/github-mcp-server]: https://github.com/github/github-mcp-server diff --git a/third-party/github.com/google/go-github/v71/github/LICENSE b/third-party/github.com/google/go-github/v71/github/LICENSE deleted file mode 100644 index 28b6486f0..000000000 --- a/third-party/github.com/google/go-github/v71/github/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2013 The go-github AUTHORS. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third-party/github.com/gorilla/mux/LICENSE b/third-party/github.com/gorilla/mux/LICENSE deleted file mode 100644 index 6903df638..000000000 --- a/third-party/github.com/gorilla/mux/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third-party/github.com/migueleliasweb/go-github-mock/src/mock/LICENSE b/third-party/github.com/migueleliasweb/go-github-mock/src/mock/LICENSE deleted file mode 100644 index 86d42717d..000000000 --- a/third-party/github.com/migueleliasweb/go-github-mock/src/mock/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 Miguel Elias dos Santos - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/third-party/golang.org/x/time/rate/LICENSE b/third-party/golang.org/x/time/rate/LICENSE deleted file mode 100644 index 6a66aea5e..000000000 --- a/third-party/golang.org/x/time/rate/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.