diff --git a/pkg/github/actions_test.go b/pkg/github/actions_test.go index aca7a5d4f..e0ece002d 100644 --- a/pkg/github/actions_test.go +++ b/pkg/github/actions_test.go @@ -18,7 +18,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" ) @@ -46,44 +45,41 @@ func Test_ListWorkflows(t *testing.T) { }{ { name: "successful workflow listing", - 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(123)), - Name: github.Ptr("CI"), - Path: github.Ptr(".github/workflows/ci.yml"), - State: github.Ptr("active"), - CreatedAt: &github.Timestamp{}, - UpdatedAt: &github.Timestamp{}, - URL: github.Ptr("https://api.github.com/repos/owner/repo/actions/workflows/123"), - HTMLURL: github.Ptr("https://github.com/owner/repo/actions/workflows/ci.yml"), - BadgeURL: github.Ptr("https://github.com/owner/repo/workflows/CI/badge.svg"), - NodeID: github.Ptr("W_123"), - }, - { - ID: github.Ptr(int64(456)), - Name: github.Ptr("Deploy"), - Path: github.Ptr(".github/workflows/deploy.yml"), - State: github.Ptr("active"), - CreatedAt: &github.Timestamp{}, - UpdatedAt: &github.Timestamp{}, - URL: github.Ptr("https://api.github.com/repos/owner/repo/actions/workflows/456"), - HTMLURL: github.Ptr("https://github.com/owner/repo/actions/workflows/deploy.yml"), - BadgeURL: github.Ptr("https://github.com/owner/repo/workflows/Deploy/badge.svg"), - NodeID: github.Ptr("W_456"), - }, + 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(123)), + Name: github.Ptr("CI"), + Path: github.Ptr(".github/workflows/ci.yml"), + State: github.Ptr("active"), + CreatedAt: &github.Timestamp{}, + UpdatedAt: &github.Timestamp{}, + URL: github.Ptr("https://api.github.com/repos/owner/repo/actions/workflows/123"), + HTMLURL: github.Ptr("https://github.com/owner/repo/actions/workflows/ci.yml"), + BadgeURL: github.Ptr("https://github.com/owner/repo/workflows/CI/badge.svg"), + NodeID: github.Ptr("W_123"), + }, + { + ID: github.Ptr(int64(456)), + Name: github.Ptr("Deploy"), + Path: github.Ptr(".github/workflows/deploy.yml"), + State: github.Ptr("active"), + CreatedAt: &github.Timestamp{}, + UpdatedAt: &github.Timestamp{}, + URL: github.Ptr("https://api.github.com/repos/owner/repo/actions/workflows/456"), + HTMLURL: github.Ptr("https://github.com/owner/repo/actions/workflows/deploy.yml"), + BadgeURL: github.Ptr("https://github.com/owner/repo/workflows/Deploy/badge.svg"), + NodeID: github.Ptr("W_456"), }, - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(workflows) - }), - ), - ), + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(workflows) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -92,7 +88,7 @@ func Test_ListWorkflows(t *testing.T) { }, { name: "missing required parameter owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "repo": "repo", }, @@ -161,14 +157,11 @@ func Test_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{ "owner": "owner", "repo": "repo", @@ -179,7 +172,7 @@ func Test_RunWorkflow(t *testing.T) { }, { name: "missing required parameter workflow_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -239,14 +232,11 @@ func Test_RunWorkflow_WithFilename(t *testing.T) { }{ { name: "successful workflow run by filename", - 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{ "owner": "owner", "repo": "repo", @@ -257,14 +247,11 @@ func Test_RunWorkflow_WithFilename(t *testing.T) { }, { name: "successful workflow run by numeric ID as string", - 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{ "owner": "owner", "repo": "repo", @@ -275,7 +262,7 @@ func Test_RunWorkflow_WithFilename(t *testing.T) { }, { name: "missing required parameter workflow_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -343,17 +330,11 @@ func Test_CancelWorkflowRun(t *testing.T) { }{ { name: "successful workflow run cancellation", - 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{ + "POST /repos/owner/repo/actions/runs/12345/cancel": http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusAccepted) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -363,17 +344,11 @@ func Test_CancelWorkflowRun(t *testing.T) { }, { name: "conflict when cancelling a workflow run", - 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{ + "POST /repos/owner/repo/actions/runs/12345/cancel": http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusConflict) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -384,7 +359,7 @@ func Test_CancelWorkflowRun(t *testing.T) { }, { name: "missing required parameter run_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -453,58 +428,55 @@ func Test_ListWorkflowRunArtifacts(t *testing.T) { }{ { name: "successful artifacts listing", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsRunsArtifactsByOwnerByRepoByRunId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - artifacts := &github.ArtifactList{ - TotalCount: github.Ptr(int64(2)), - Artifacts: []*github.Artifact{ - { - ID: github.Ptr(int64(1)), - NodeID: github.Ptr("A_1"), - Name: github.Ptr("build-artifacts"), - SizeInBytes: github.Ptr(int64(1024)), - URL: github.Ptr("https://api.github.com/repos/owner/repo/actions/artifacts/1"), - ArchiveDownloadURL: github.Ptr("https://api.github.com/repos/owner/repo/actions/artifacts/1/zip"), - Expired: github.Ptr(false), - CreatedAt: &github.Timestamp{}, - UpdatedAt: &github.Timestamp{}, - ExpiresAt: &github.Timestamp{}, - WorkflowRun: &github.ArtifactWorkflowRun{ - ID: github.Ptr(int64(12345)), - RepositoryID: github.Ptr(int64(1)), - HeadRepositoryID: github.Ptr(int64(1)), - HeadBranch: github.Ptr("main"), - HeadSHA: github.Ptr("abc123"), - }, + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsArtifactsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + artifacts := &github.ArtifactList{ + TotalCount: github.Ptr(int64(2)), + Artifacts: []*github.Artifact{ + { + ID: github.Ptr(int64(1)), + NodeID: github.Ptr("A_1"), + Name: github.Ptr("build-artifacts"), + SizeInBytes: github.Ptr(int64(1024)), + URL: github.Ptr("https://api.github.com/repos/owner/repo/actions/artifacts/1"), + ArchiveDownloadURL: github.Ptr("https://api.github.com/repos/owner/repo/actions/artifacts/1/zip"), + Expired: github.Ptr(false), + CreatedAt: &github.Timestamp{}, + UpdatedAt: &github.Timestamp{}, + ExpiresAt: &github.Timestamp{}, + WorkflowRun: &github.ArtifactWorkflowRun{ + ID: github.Ptr(int64(12345)), + RepositoryID: github.Ptr(int64(1)), + HeadRepositoryID: github.Ptr(int64(1)), + HeadBranch: github.Ptr("main"), + HeadSHA: github.Ptr("abc123"), }, - { - ID: github.Ptr(int64(2)), - NodeID: github.Ptr("A_2"), - Name: github.Ptr("test-results"), - SizeInBytes: github.Ptr(int64(512)), - URL: github.Ptr("https://api.github.com/repos/owner/repo/actions/artifacts/2"), - ArchiveDownloadURL: github.Ptr("https://api.github.com/repos/owner/repo/actions/artifacts/2/zip"), - Expired: github.Ptr(false), - CreatedAt: &github.Timestamp{}, - UpdatedAt: &github.Timestamp{}, - ExpiresAt: &github.Timestamp{}, - WorkflowRun: &github.ArtifactWorkflowRun{ - ID: github.Ptr(int64(12345)), - RepositoryID: github.Ptr(int64(1)), - HeadRepositoryID: github.Ptr(int64(1)), - HeadBranch: github.Ptr("main"), - HeadSHA: github.Ptr("abc123"), - }, + }, + { + ID: github.Ptr(int64(2)), + NodeID: github.Ptr("A_2"), + Name: github.Ptr("test-results"), + SizeInBytes: github.Ptr(int64(512)), + URL: github.Ptr("https://api.github.com/repos/owner/repo/actions/artifacts/2"), + ArchiveDownloadURL: github.Ptr("https://api.github.com/repos/owner/repo/actions/artifacts/2/zip"), + Expired: github.Ptr(false), + CreatedAt: &github.Timestamp{}, + UpdatedAt: &github.Timestamp{}, + ExpiresAt: &github.Timestamp{}, + WorkflowRun: &github.ArtifactWorkflowRun{ + ID: github.Ptr(int64(12345)), + RepositoryID: github.Ptr(int64(1)), + HeadRepositoryID: github.Ptr(int64(1)), + HeadBranch: github.Ptr("main"), + HeadSHA: github.Ptr("abc123"), }, }, - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(artifacts) - }), - ), - ), + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(artifacts) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -514,7 +486,7 @@ func Test_ListWorkflowRunArtifacts(t *testing.T) { }, { name: "missing required parameter run_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -582,19 +554,13 @@ func Test_DownloadWorkflowRunArtifact(t *testing.T) { }{ { name: "successful artifact download URL", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/actions/artifacts/123/zip", - Method: "GET", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - // GitHub returns a 302 redirect to the download URL - w.Header().Set("Location", "https://api.github.com/repos/owner/repo/actions/artifacts/123/download") - w.WriteHeader(http.StatusFound) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "GET /repos/owner/repo/actions/artifacts/123/zip": http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + // GitHub returns a 302 redirect to the download URL + w.Header().Set("Location", "https://api.github.com/repos/owner/repo/actions/artifacts/123/download") + w.WriteHeader(http.StatusFound) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -604,7 +570,7 @@ func Test_DownloadWorkflowRunArtifact(t *testing.T) { }, { name: "missing required parameter artifact_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -673,14 +639,11 @@ func Test_DeleteWorkflowRunLogs(t *testing.T) { }{ { name: "successful logs deletion", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.DeleteReposActionsRunsLogsByOwnerByRepoByRunId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNoContent) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteReposActionsRunsLogsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -690,7 +653,7 @@ func Test_DeleteWorkflowRunLogs(t *testing.T) { }, { name: "missing required parameter run_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -757,34 +720,31 @@ func Test_GetWorkflowRunUsage(t *testing.T) { }{ { name: "successful workflow run usage", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsRunsTimingByOwnerByRepoByRunId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - usage := &github.WorkflowRunUsage{ - Billable: &github.WorkflowRunBillMap{ - "UBUNTU": &github.WorkflowRunBill{ - TotalMS: github.Ptr(int64(120000)), - Jobs: github.Ptr(2), - JobRuns: []*github.WorkflowRunJobRun{ - { - JobID: github.Ptr(1), - DurationMS: github.Ptr(int64(60000)), - }, - { - JobID: github.Ptr(2), - DurationMS: github.Ptr(int64(60000)), - }, + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsTimingByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + usage := &github.WorkflowRunUsage{ + Billable: &github.WorkflowRunBillMap{ + "UBUNTU": &github.WorkflowRunBill{ + TotalMS: github.Ptr(int64(120000)), + Jobs: github.Ptr(2), + JobRuns: []*github.WorkflowRunJobRun{ + { + JobID: github.Ptr(1), + DurationMS: github.Ptr(int64(60000)), + }, + { + JobID: github.Ptr(2), + DurationMS: github.Ptr(int64(60000)), }, }, }, - RunDurationMS: github.Ptr(int64(120000)), - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(usage) - }), - ), - ), + }, + RunDurationMS: github.Ptr(int64(120000)), + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(usage) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -794,7 +754,7 @@ func Test_GetWorkflowRunUsage(t *testing.T) { }, { name: "missing required parameter run_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -865,15 +825,12 @@ func Test_GetJobLogs(t *testing.T) { }{ { name: "successful single job logs with URL", - 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) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -889,42 +846,36 @@ func Test_GetJobLogs(t *testing.T) { }, { name: "successful failed jobs logs", - 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"), + }, + { + 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) - }), - ), - 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) - }), - ), - ), + }, + } + 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) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -946,30 +897,27 @@ func Test_GetJobLogs(t *testing.T) { }, { name: "no failed jobs found", - 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"), + }, + { + ID: github.Ptr(int64(2)), + Name: github.Ptr("test-job-2"), + Conclusion: github.Ptr("success"), }, - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(jobs) - }), - ), - ), + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(jobs) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -986,7 +934,7 @@ func Test_GetJobLogs(t *testing.T) { }, { name: "missing job_id when not using failed_only", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -996,7 +944,7 @@ func Test_GetJobLogs(t *testing.T) { }, { name: "missing run_id when using failed_only", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1007,7 +955,7 @@ func Test_GetJobLogs(t *testing.T) { }, { name: "missing required parameter owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "repo": "repo", "job_id": float64(123), @@ -1017,7 +965,7 @@ func Test_GetJobLogs(t *testing.T) { }, { name: "missing required parameter repo", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "job_id": float64(123), @@ -1027,17 +975,14 @@ func Test_GetJobLogs(t *testing.T) { }, { name: "API error when getting single job logs", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsJobsLogsByOwnerByRepoByJobId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _ = json.NewEncoder(w).Encode(map[string]string{ - "message": "Not Found", - }) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsJobsLogsByOwnerByRepoByJobID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _ = json.NewEncoder(w).Encode(map[string]string{ + "message": "Not Found", + }) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1047,17 +992,14 @@ func Test_GetJobLogs(t *testing.T) { }, { name: "API error when listing workflow jobs for failed_only", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsRunsJobsByOwnerByRepoByRunId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _ = json.NewEncoder(w).Encode(map[string]string{ - "message": "Not Found", - }) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsJobsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _ = json.NewEncoder(w).Encode(map[string]string{ + "message": "Not Found", + }) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1124,15 +1066,12 @@ func Test_GetJobLogs_WithContentReturn(t *testing.T) { })) defer testServer.Close() - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsJobsLogsByOwnerByRepoByJobId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Location", testServer.URL) - w.WriteHeader(http.StatusFound) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsJobsLogsByOwnerByRepoByJobID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Location", testServer.URL) + w.WriteHeader(http.StatusFound) + }), + }) client := github.NewClient(mockedClient) toolDef := GetJobLogs(translations.NullTranslationHelper) @@ -1176,15 +1115,12 @@ func Test_GetJobLogs_WithContentReturnAndTailLines(t *testing.T) { })) defer testServer.Close() - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsJobsLogsByOwnerByRepoByJobId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Location", testServer.URL) - w.WriteHeader(http.StatusFound) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsJobsLogsByOwnerByRepoByJobID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Location", testServer.URL) + w.WriteHeader(http.StatusFound) + }), + }) client := github.NewClient(mockedClient) toolDef := GetJobLogs(translations.NullTranslationHelper) @@ -1228,15 +1164,12 @@ func Test_GetJobLogs_WithContentReturnAndLargeTailLines(t *testing.T) { })) defer testServer.Close() - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsJobsLogsByOwnerByRepoByJobId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Location", testServer.URL) - w.WriteHeader(http.StatusFound) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsJobsLogsByOwnerByRepoByJobID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Location", testServer.URL) + w.WriteHeader(http.StatusFound) + }), + }) client := github.NewClient(mockedClient) toolDef := GetJobLogs(translations.NullTranslationHelper) diff --git a/pkg/github/context_tools_test.go b/pkg/github/context_tools_test.go index 5f471ff37..3f4261e71 100644 --- a/pkg/github/context_tools_test.go +++ b/pkg/github/context_tools_test.go @@ -11,7 +11,6 @@ import ( "github.com/github/github-mcp-server/internal/toolsnaps" "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v79/github" - "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -57,24 +56,18 @@ func Test_GetMe(t *testing.T) { }{ { name: "successful get user", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetUser, - mockUser, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUser: mockResponse(t, http.StatusOK, mockUser), + }), requestArgs: map[string]any{}, expectToolError: false, expectedUser: mockUser, }, { name: "successful get user with reason", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetUser, - mockUser, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUser: mockResponse(t, http.StatusOK, mockUser), + }), requestArgs: map[string]any{ "reason": "Testing API", }, @@ -90,12 +83,9 @@ func Test_GetMe(t *testing.T) { }, { name: "get user fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetUser, - badRequestHandler("expected test failure"), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUser: badRequestHandler("expected test failure"), + }), requestArgs: map[string]any{}, expectToolError: true, expectedToolErrMsg: "expected test failure", @@ -255,21 +245,15 @@ func Test_GetTeams(t *testing.T) { // Factory function for mock HTTP clients with user response httpClientWithUser := func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetUser, - mockUser, - ), - ) + return MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUser: mockResponse(t, http.StatusOK, mockUser), + }) } httpClientUserFails := func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetUser, - badRequestHandler("expected test failure"), - ), - ) + return MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUser: badRequestHandler("expected test failure"), + }) } tests := []struct { diff --git a/pkg/github/dependabot_test.go b/pkg/github/dependabot_test.go index 0ceac4ffa..e57405a8c 100644 --- a/pkg/github/dependabot_test.go +++ b/pkg/github/dependabot_test.go @@ -9,7 +9,6 @@ import ( "github.com/github/github-mcp-server/internal/toolsnaps" "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v79/github" - "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -42,12 +41,9 @@ func Test_GetDependabotAlert(t *testing.T) { }{ { name: "successful alert fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposDependabotAlertsByOwnerByRepoByAlertNumber, - mockAlert, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposDependabotAlertsByOwnerByRepoByAlertNumber: mockResponse(t, http.StatusOK, mockAlert), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -58,15 +54,12 @@ func Test_GetDependabotAlert(t *testing.T) { }, { name: "alert fetch fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposDependabotAlertsByOwnerByRepoByAlertNumber, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposDependabotAlertsByOwnerByRepoByAlertNumber: 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", @@ -154,16 +147,13 @@ func Test_ListDependabotAlerts(t *testing.T) { }{ { name: "successful open alerts listing", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposDependabotAlertsByOwnerByRepo, - expectQueryParams(t, map[string]string{ - "state": "open", - }).andThen( - mockResponse(t, http.StatusOK, []*github.DependabotAlert{&criticalAlert}), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposDependabotAlertsByOwnerByRepo: expectQueryParams(t, map[string]string{ + "state": "open", + }).andThen( + mockResponse(t, http.StatusOK, []*github.DependabotAlert{&criticalAlert}), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -174,16 +164,13 @@ func Test_ListDependabotAlerts(t *testing.T) { }, { name: "successful severity filtered listing", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposDependabotAlertsByOwnerByRepo, - expectQueryParams(t, map[string]string{ - "severity": "high", - }).andThen( - mockResponse(t, http.StatusOK, []*github.DependabotAlert{&highSeverityAlert}), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposDependabotAlertsByOwnerByRepo: expectQueryParams(t, map[string]string{ + "severity": "high", + }).andThen( + mockResponse(t, http.StatusOK, []*github.DependabotAlert{&highSeverityAlert}), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -194,14 +181,11 @@ func Test_ListDependabotAlerts(t *testing.T) { }, { name: "successful all alerts listing", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposDependabotAlertsByOwnerByRepo, - expectQueryParams(t, map[string]string{}).andThen( - mockResponse(t, http.StatusOK, []*github.DependabotAlert{&criticalAlert, &highSeverityAlert}), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposDependabotAlertsByOwnerByRepo: expectQueryParams(t, map[string]string{}).andThen( + mockResponse(t, http.StatusOK, []*github.DependabotAlert{&criticalAlert, &highSeverityAlert}), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -211,15 +195,12 @@ func Test_ListDependabotAlerts(t *testing.T) { }, { name: "alerts listing fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposDependabotAlertsByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnauthorized) - _, _ = w.Write([]byte(`{"message": "Unauthorized access"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposDependabotAlertsByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte(`{"message": "Unauthorized access"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", diff --git a/pkg/github/gists_test.go b/pkg/github/gists_test.go index 886db4a1a..0dd112afb 100644 --- a/pkg/github/gists_test.go +++ b/pkg/github/gists_test.go @@ -11,7 +11,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" ) @@ -77,24 +76,18 @@ func Test_ListGists(t *testing.T) { }{ { name: "list authenticated user's gists", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetGists, - mockGists, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetGists: mockResponse(t, http.StatusOK, mockGists), + }), requestArgs: map[string]interface{}{}, expectError: false, expectedGists: mockGists, }, { name: "list specific user's gists", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetUsersGistsByUsername, - mockResponse(t, http.StatusOK, mockGists), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUsersGistsByUsername: mockResponse(t, http.StatusOK, mockGists), + }), requestArgs: map[string]interface{}{ "username": "testuser", }, @@ -103,18 +96,15 @@ func Test_ListGists(t *testing.T) { }, { name: "list gists with pagination and since parameter", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetGists, - expectQueryParams(t, map[string]string{ - "since": "2023-01-01T00:00:00Z", - "page": "2", - "per_page": "5", - }).andThen( - mockResponse(t, http.StatusOK, mockGists), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetGists: expectQueryParams(t, map[string]string{ + "since": "2023-01-01T00:00:00Z", + "page": "2", + "per_page": "5", + }).andThen( + mockResponse(t, http.StatusOK, mockGists), ), - ), + }), requestArgs: map[string]interface{}{ "since": "2023-01-01T00:00:00Z", "page": float64(2), @@ -125,12 +115,9 @@ func Test_ListGists(t *testing.T) { }, { name: "invalid since parameter", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetGists, - mockGists, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetGists: mockResponse(t, http.StatusOK, mockGists), + }), requestArgs: map[string]interface{}{ "since": "invalid-date", }, @@ -139,15 +126,12 @@ func Test_ListGists(t *testing.T) { }, { name: "list gists fails with error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetGists, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnauthorized) - _, _ = w.Write([]byte(`{"message": "Requires authentication"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetGists: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte(`{"message": "Requires authentication"}`)) + }), + }), requestArgs: map[string]interface{}{}, expectError: true, expectedErrMsg: "failed to list gists", @@ -242,12 +226,9 @@ func Test_GetGist(t *testing.T) { }{ { name: "Successful fetching different gist", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetGistsByGistId, - mockResponse(t, http.StatusOK, mockGist), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetGistsByGistID: mockResponse(t, http.StatusOK, mockGist), + }), requestArgs: map[string]interface{}{ "gist_id": "gist1", }, @@ -256,15 +237,12 @@ func Test_GetGist(t *testing.T) { }, { name: "gist_id parameter missing", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetGistsByGistId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnprocessableEntity) - _, _ = w.Write([]byte(`{"message": "Invalid Request"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetGistsByGistID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"message": "Invalid Request"}`)) + }), + }), requestArgs: map[string]interface{}{}, expectError: true, expectedErrMsg: "missing required parameter: gist_id", @@ -361,12 +339,9 @@ func Test_CreateGist(t *testing.T) { }{ { name: "create gist successfully", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostGists, - mockResponse(t, http.StatusCreated, createdGist), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostGists: mockResponse(t, http.StatusCreated, createdGist), + }), requestArgs: map[string]interface{}{ "filename": "test.go", "content": "package main\n\nfunc main() {\n\tfmt.Println(\"Hello, Gist!\")\n}", @@ -378,7 +353,7 @@ func Test_CreateGist(t *testing.T) { }, { name: "missing required filename", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "content": "test content", "description": "Test Gist", @@ -388,7 +363,7 @@ func Test_CreateGist(t *testing.T) { }, { name: "missing required content", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "filename": "test.go", "description": "Test Gist", @@ -398,15 +373,12 @@ func Test_CreateGist(t *testing.T) { }, { name: "api returns error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostGists, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnauthorized) - _, _ = w.Write([]byte(`{"message": "Requires authentication"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostGists: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte(`{"message": "Requires authentication"}`)) + }), + }), requestArgs: map[string]interface{}{ "filename": "test.go", "content": "package main", @@ -506,12 +478,9 @@ func Test_UpdateGist(t *testing.T) { }{ { name: "update gist successfully", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchGistsByGistId, - mockResponse(t, http.StatusOK, updatedGist), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchGistsByGistID: mockResponse(t, http.StatusOK, updatedGist), + }), requestArgs: map[string]interface{}{ "gist_id": "existing-gist-id", "filename": "updated.go", @@ -523,7 +492,7 @@ func Test_UpdateGist(t *testing.T) { }, { name: "missing required gist_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "filename": "updated.go", "content": "updated content", @@ -534,7 +503,7 @@ func Test_UpdateGist(t *testing.T) { }, { name: "missing required filename", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "gist_id": "existing-gist-id", "content": "updated content", @@ -545,7 +514,7 @@ func Test_UpdateGist(t *testing.T) { }, { name: "missing required content", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "gist_id": "existing-gist-id", "filename": "updated.go", @@ -556,15 +525,12 @@ func Test_UpdateGist(t *testing.T) { }, { name: "api returns error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchGistsByGistId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchGistsByGistID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), requestArgs: map[string]interface{}{ "gist_id": "nonexistent-gist-id", "filename": "updated.go", diff --git a/pkg/github/helper_test.go b/pkg/github/helper_test.go index 3eea4fba7..972903520 100644 --- a/pkg/github/helper_test.go +++ b/pkg/github/helper_test.go @@ -18,15 +18,122 @@ import ( // GitHub API endpoint patterns for testing // These constants define the URL patterns used in HTTP mocking for tests const ( + // User endpoints + GetUser = "GET /user" + GetUserStarred = "GET /user/starred" + GetUsersGistsByUsername = "GET /users/{username}/gists" + GetUsersStarredByUsername = "GET /users/{username}/starred" + PutUserStarredByOwnerByRepo = "PUT /user/starred/{owner}/{repo}" + DeleteUserStarredByOwnerByRepo = "DELETE /user/starred/{owner}/{repo}" + // Repository endpoints - GetReposByOwnerByRepo = "GET /repos/{owner}/{repo}" + GetReposByOwnerByRepo = "GET /repos/{owner}/{repo}" + GetReposBranchesByOwnerByRepo = "GET /repos/{owner}/{repo}/branches" + 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}" + PostReposForksByOwnerByRepo = "POST /repos/{owner}/{repo}/forks" + GetReposSubscriptionByOwnerByRepo = "GET /repos/{owner}/{repo}/subscription" + PutReposSubscriptionByOwnerByRepo = "PUT /repos/{owner}/{repo}/subscription" + DeleteReposSubscriptionByOwnerByRepo = "DELETE /repos/{owner}/{repo}/subscription" // Git endpoints - GetReposGitTreesByOwnerByRepoByTree = "GET /repos/{owner}/{repo}/git/trees/{tree}" + GetReposGitTreesByOwnerByRepoByTree = "GET /repos/{owner}/{repo}/git/trees/{tree}" + GetReposGitRefByOwnerByRepoByRef = "GET /repos/{owner}/{repo}/git/ref/{ref}" + PostReposGitRefsByOwnerByRepo = "POST /repos/{owner}/{repo}/git/refs" + 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}" + PostReposGitTreesByOwnerByRepo = "POST /repos/{owner}/{repo}/git/trees" + GetReposCommitsStatusByOwnerByRepoByRef = "GET /repos/{owner}/{repo}/commits/{ref}/status" + GetReposCommitsStatusesByOwnerByRepoByRef = "GET /repos/{owner}/{repo}/commits/{ref}/statuses" + + // Issues endpoints + GetReposIssuesByOwnerByRepoByIssueNumber = "GET /repos/{owner}/{repo}/issues/{issue_number}" + GetReposIssuesCommentsByOwnerByRepoByIssueNumber = "GET /repos/{owner}/{repo}/issues/{issue_number}/comments" + PostReposIssuesByOwnerByRepo = "POST /repos/{owner}/{repo}/issues" + PostReposIssuesCommentsByOwnerByRepoByIssueNumber = "POST /repos/{owner}/{repo}/issues/{issue_number}/comments" + 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" + PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber = "PATCH /repos/{owner}/{repo}/issues/{issue_number}/sub_issues/priority" + + // Pull request endpoints + GetReposPullsByOwnerByRepo = "GET /repos/{owner}/{repo}/pulls" + GetReposPullsByOwnerByRepoByPullNumber = "GET /repos/{owner}/{repo}/pulls/{pull_number}" + GetReposPullsFilesByOwnerByRepoByPullNumber = "GET /repos/{owner}/{repo}/pulls/{pull_number}/files" + GetReposPullsReviewsByOwnerByRepoByPullNumber = "GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews" + PostReposPullsByOwnerByRepo = "POST /repos/{owner}/{repo}/pulls" + PatchReposPullsByOwnerByRepoByPullNumber = "PATCH /repos/{owner}/{repo}/pulls/{pull_number}" + PutReposPullsMergeByOwnerByRepoByPullNumber = "PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge" + PutReposPullsUpdateBranchByOwnerByRepoByPullNumber = "PUT /repos/{owner}/{repo}/pulls/{pull_number}/update-branch" + PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber = "POST /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers" + + // Notifications endpoints + GetNotifications = "GET /notifications" + PutNotifications = "PUT /notifications" + GetReposNotificationsByOwnerByRepo = "GET /repos/{owner}/{repo}/notifications" + PutReposNotificationsByOwnerByRepo = "PUT /repos/{owner}/{repo}/notifications" + GetNotificationsThreadsByThreadID = "GET /notifications/threads/{thread_id}" + PatchNotificationsThreadsByThreadID = "PATCH /notifications/threads/{thread_id}" + DeleteNotificationsThreadsByThreadID = "DELETE /notifications/threads/{thread_id}" + PutNotificationsThreadsSubscriptionByThreadID = "PUT /notifications/threads/{thread_id}/subscription" + DeleteNotificationsThreadsSubscriptionByThreadID = "DELETE /notifications/threads/{thread_id}/subscription" + + // Gists endpoints + GetGists = "GET /gists" + GetGistsByGistID = "GET /gists/{gist_id}" + PostGists = "POST /gists" + PatchGistsByGistID = "PATCH /gists/{gist_id}" + + // Releases endpoints + GetReposReleasesByOwnerByRepo = "GET /repos/{owner}/{repo}/releases" + GetReposReleasesLatestByOwnerByRepo = "GET /repos/{owner}/{repo}/releases/latest" + GetReposReleasesTagsByOwnerByRepoByTag = "GET /repos/{owner}/{repo}/releases/tags/{tag}" // Code scanning endpoints GetReposCodeScanningAlertsByOwnerByRepo = "GET /repos/{owner}/{repo}/code-scanning/alerts" GetReposCodeScanningAlertsByOwnerByRepoByAlertNumber = "GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}" + + // Secret scanning endpoints + GetReposSecretScanningAlertsByOwnerByRepo = "GET /repos/{owner}/{repo}/secret-scanning/alerts" //nolint:gosec // False positive - this is an API endpoint pattern, not a credential + GetReposSecretScanningAlertsByOwnerByRepoByAlertNumber = "GET /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}" //nolint:gosec // False positive - this is an API endpoint pattern, not a credential + + // Dependabot endpoints + GetReposDependabotAlertsByOwnerByRepo = "GET /repos/{owner}/{repo}/dependabot/alerts" + GetReposDependabotAlertsByOwnerByRepoByAlertNumber = "GET /repos/{owner}/{repo}/dependabot/alerts/{alert_number}" + + // Security advisories endpoints + GetAdvisories = "GET /advisories" + GetAdvisoriesByGhsaID = "GET /advisories/{ghsa_id}" + GetReposSecurityAdvisoriesByOwnerByRepo = "GET /repos/{owner}/{repo}/security-advisories" + GetOrgsSecurityAdvisoriesByOrg = "GET /orgs/{org}/security-advisories" + + // Actions endpoints + GetReposActionsWorkflowsByOwnerByRepo = "GET /repos/{owner}/{repo}/actions/workflows" + PostReposActionsWorkflowsDispatchesByOwnerByRepoByWorkflowID = "POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches" + GetReposActionsRunsJobsByOwnerByRepoByRunID = "GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs" + GetReposActionsRunsArtifactsByOwnerByRepoByRunID = "GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts" + GetReposActionsRunsTimingByOwnerByRepoByRunID = "GET /repos/{owner}/{repo}/actions/runs/{run_id}/timing" + GetReposActionsJobsLogsByOwnerByRepoByJobID = "GET /repos/{owner}/{repo}/actions/jobs/{job_id}/logs" + DeleteReposActionsRunsLogsByOwnerByRepoByRunID = "DELETE /repos/{owner}/{repo}/actions/runs/{run_id}/logs" + + // Search endpoints + GetSearchCode = "GET /search/code" + GetSearchIssues = "GET /search/issues" + GetSearchRepositories = "GET /search/repositories" + GetSearchUsers = "GET /search/users" + + // Raw content endpoints (used for GitHub raw content API, not standard API) + // These are used with the raw content client that interacts with raw.githubusercontent.com + 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:.*}" ) type expectations struct { @@ -382,6 +489,27 @@ func matchPath(pattern, path string) bool { patternParts := strings.Split(strings.Trim(pattern, "/"), "/") pathParts := strings.Split(strings.Trim(path, "/"), "/") + // Handle patterns with wildcard path like {path:.*} + if len(patternParts) > 0 { + lastPart := patternParts[len(patternParts)-1] + if strings.HasPrefix(lastPart, "{") && strings.Contains(lastPart, ":") && strings.HasSuffix(lastPart, "}") { + // This is a wildcard pattern like {path:.*} + // Check if all parts before the wildcard match + if len(pathParts) < len(patternParts)-1 { + return false + } + for i := 0; i < len(patternParts)-1; i++ { + if strings.HasPrefix(patternParts[i], "{") && strings.HasSuffix(patternParts[i], "}") { + continue // Path parameter matches anything + } + if patternParts[i] != pathParts[i] { + return false + } + } + return true + } + } + if len(patternParts) != len(pathParts) { return false } diff --git a/pkg/github/notifications_test.go b/pkg/github/notifications_test.go index 1b12c911f..936a70df4 100644 --- a/pkg/github/notifications_test.go +++ b/pkg/github/notifications_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" ) @@ -50,24 +49,18 @@ func Test_ListNotifications(t *testing.T) { }{ { name: "success default filter (no params)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetNotifications, - []*github.Notification{mockNotification}, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetNotifications: mockResponse(t, http.StatusOK, []*github.Notification{mockNotification}), + }), requestArgs: map[string]interface{}{}, expectError: false, expectedResult: []*github.Notification{mockNotification}, }, { name: "success with filter=include_read_notifications", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetNotifications, - []*github.Notification{mockNotification}, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetNotifications: mockResponse(t, http.StatusOK, []*github.Notification{mockNotification}), + }), requestArgs: map[string]interface{}{ "filter": "include_read_notifications", }, @@ -76,12 +69,9 @@ func Test_ListNotifications(t *testing.T) { }, { name: "success with filter=only_participating", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetNotifications, - []*github.Notification{mockNotification}, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetNotifications: mockResponse(t, http.StatusOK, []*github.Notification{mockNotification}), + }), requestArgs: map[string]interface{}{ "filter": "only_participating", }, @@ -90,12 +80,9 @@ func Test_ListNotifications(t *testing.T) { }, { name: "success for repo notifications", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposNotificationsByOwnerByRepo, - []*github.Notification{mockNotification}, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposNotificationsByOwnerByRepo: mockResponse(t, http.StatusOK, []*github.Notification{mockNotification}), + }), requestArgs: map[string]interface{}{ "filter": "default", "since": "2024-01-01T00:00:00Z", @@ -110,12 +97,9 @@ func Test_ListNotifications(t *testing.T) { }, { name: "error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetNotifications, - mockResponse(t, http.StatusInternalServerError, `{"message": "error"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetNotifications: mockResponse(t, http.StatusInternalServerError, `{"message": "error"}`), + }), requestArgs: map[string]interface{}{}, expectError: true, expectedErrMsg: "error", @@ -184,12 +168,9 @@ func Test_ManageNotificationSubscription(t *testing.T) { }{ { name: "ignore subscription", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.PutNotificationsThreadsSubscriptionByThreadId, - mockSub, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutNotificationsThreadsSubscriptionByThreadID: mockResponse(t, http.StatusOK, mockSub), + }), requestArgs: map[string]interface{}{ "notificationID": "123", "action": "ignore", @@ -199,12 +180,9 @@ func Test_ManageNotificationSubscription(t *testing.T) { }, { name: "watch subscription", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.PutNotificationsThreadsSubscriptionByThreadId, - mockSubWatch, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutNotificationsThreadsSubscriptionByThreadID: mockResponse(t, http.StatusOK, mockSubWatch), + }), requestArgs: map[string]interface{}{ "notificationID": "123", "action": "watch", @@ -214,12 +192,9 @@ func Test_ManageNotificationSubscription(t *testing.T) { }, { name: "delete subscription", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.DeleteNotificationsThreadsSubscriptionByThreadId, - nil, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteNotificationsThreadsSubscriptionByThreadID: mockResponse(t, http.StatusOK, nil), + }), requestArgs: map[string]interface{}{ "notificationID": "123", "action": "delete", @@ -229,7 +204,7 @@ func Test_ManageNotificationSubscription(t *testing.T) { }, { name: "invalid action", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "notificationID": "123", "action": "invalid", @@ -239,7 +214,7 @@ func Test_ManageNotificationSubscription(t *testing.T) { }, { name: "missing required notificationID", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "action": "ignore", }, @@ -247,7 +222,7 @@ func Test_ManageNotificationSubscription(t *testing.T) { }, { name: "missing required action", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "notificationID": "123", }, @@ -331,12 +306,9 @@ func Test_ManageRepositoryNotificationSubscription(t *testing.T) { }{ { name: "ignore subscription", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.PutReposSubscriptionByOwnerByRepo, - mockSub, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutReposSubscriptionByOwnerByRepo: mockResponse(t, http.StatusOK, mockSub), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -347,12 +319,9 @@ func Test_ManageRepositoryNotificationSubscription(t *testing.T) { }, { name: "watch subscription", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.PutReposSubscriptionByOwnerByRepo, - mockWatchSub, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutReposSubscriptionByOwnerByRepo: mockResponse(t, http.StatusOK, mockWatchSub), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -364,12 +333,9 @@ func Test_ManageRepositoryNotificationSubscription(t *testing.T) { }, { name: "delete subscription", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.DeleteReposSubscriptionByOwnerByRepo, - nil, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteReposSubscriptionByOwnerByRepo: mockResponse(t, http.StatusOK, nil), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -380,7 +346,7 @@ func Test_ManageRepositoryNotificationSubscription(t *testing.T) { }, { name: "invalid action", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -391,7 +357,7 @@ func Test_ManageRepositoryNotificationSubscription(t *testing.T) { }, { name: "missing required owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "repo": "repo", "action": "ignore", @@ -400,7 +366,7 @@ func Test_ManageRepositoryNotificationSubscription(t *testing.T) { }, { name: "missing required repo", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "owner": "owner", "action": "ignore", @@ -409,7 +375,7 @@ func Test_ManageRepositoryNotificationSubscription(t *testing.T) { }, { name: "missing required action", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -495,12 +461,9 @@ func Test_DismissNotification(t *testing.T) { }{ { name: "mark as read", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.PatchNotificationsThreadsByThreadId, - nil, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchNotificationsThreadsByThreadID: mockResponse(t, http.StatusOK, nil), + }), requestArgs: map[string]interface{}{ "threadID": "123", "state": "read", @@ -510,12 +473,9 @@ func Test_DismissNotification(t *testing.T) { }, { name: "mark as done", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.DeleteNotificationsThreadsByThreadId, - nil, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteNotificationsThreadsByThreadID: mockResponse(t, http.StatusOK, nil), + }), requestArgs: map[string]interface{}{ "threadID": "123", "state": "done", @@ -525,7 +485,7 @@ func Test_DismissNotification(t *testing.T) { }, { name: "invalid threadID format", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "threadID": "notanumber", "state": "done", @@ -535,7 +495,7 @@ func Test_DismissNotification(t *testing.T) { }, { name: "missing required threadID", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "state": "read", }, @@ -543,7 +503,7 @@ func Test_DismissNotification(t *testing.T) { }, { name: "missing required state", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "threadID": "123", }, @@ -551,7 +511,7 @@ func Test_DismissNotification(t *testing.T) { }, { name: "invalid state value", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "threadID": "123", "state": "invalid", @@ -632,24 +592,18 @@ func Test_MarkAllNotificationsRead(t *testing.T) { }{ { name: "success (no params)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.PutNotifications, - nil, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutNotifications: mockResponse(t, http.StatusOK, nil), + }), requestArgs: map[string]interface{}{}, expectError: false, expectMarked: true, }, { name: "success with lastReadAt param", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.PutNotifications, - nil, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutNotifications: mockResponse(t, http.StatusOK, nil), + }), requestArgs: map[string]interface{}{ "lastReadAt": "2024-01-01T00:00:00Z", }, @@ -658,12 +612,9 @@ func Test_MarkAllNotificationsRead(t *testing.T) { }, { name: "success with owner and repo", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.PutReposNotificationsByOwnerByRepo, - nil, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutReposNotificationsByOwnerByRepo: mockResponse(t, http.StatusOK, nil), + }), requestArgs: map[string]interface{}{ "owner": "octocat", "repo": "hello-world", @@ -673,12 +624,9 @@ func Test_MarkAllNotificationsRead(t *testing.T) { }, { name: "API error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PutNotifications, - mockResponse(t, http.StatusInternalServerError, `{"message": "error"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutNotifications: mockResponse(t, http.StatusInternalServerError, `{"message": "error"}`), + }), requestArgs: map[string]interface{}{}, expectError: true, expectedErrMsg: "error", @@ -741,12 +689,9 @@ func Test_GetNotificationDetails(t *testing.T) { }{ { name: "success", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetNotificationsThreadsByThreadId, - mockThread, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetNotificationsThreadsByThreadID: mockResponse(t, http.StatusOK, mockThread), + }), requestArgs: map[string]interface{}{ "notificationID": "123", }, @@ -755,12 +700,9 @@ func Test_GetNotificationDetails(t *testing.T) { }, { name: "not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetNotificationsThreadsByThreadId, - mockResponse(t, http.StatusNotFound, `{"message": "not found"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetNotificationsThreadsByThreadID: mockResponse(t, http.StatusNotFound, `{"message": "not found"}`), + }), requestArgs: map[string]interface{}{ "notificationID": "123", }, diff --git a/pkg/github/repository_resource_test.go b/pkg/github/repository_resource_test.go index 99c06cdd6..b55b821af 100644 --- a/pkg/github/repository_resource_test.go +++ b/pkg/github/repository_resource_test.go @@ -8,7 +8,6 @@ import ( "github.com/github/github-mcp-server/pkg/raw" "github.com/google/go-github/v79/github" - "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/require" ) @@ -34,16 +33,13 @@ func Test_repositoryResourceContents(t *testing.T) { }{ { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "text/markdown") - _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) - require.NoError(t, err) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetRawReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/markdown") + _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) + require.NoError(t, err) + }), + }), uri: "repo:///repo/contents/README.md", handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { return RepositoryResourceContentsHandler(deps, repositoryResourceContentURITemplate) @@ -53,16 +49,13 @@ func Test_repositoryResourceContents(t *testing.T) { }, { name: "missing repo", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByBranchByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "text/markdown") - _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) - require.NoError(t, err) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetRawReposContentsByOwnerByRepoByBranchByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/markdown") + _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) + require.NoError(t, err) + }), + }), uri: "repo://owner//refs/heads/main/contents/README.md", handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { return RepositoryResourceContentsHandler(deps, repositoryResourceBranchContentURITemplate) @@ -72,16 +65,13 @@ func Test_repositoryResourceContents(t *testing.T) { }, { name: "successful blob content fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "image/png") - _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) - require.NoError(t, err) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetRawReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "image/png") + _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) + require.NoError(t, err) + }), + }), uri: "repo://owner/repo/contents/data.png", handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { return RepositoryResourceContentsHandler(deps, repositoryResourceContentURITemplate) @@ -96,16 +86,13 @@ func Test_repositoryResourceContents(t *testing.T) { }, { name: "successful text content fetch (HEAD)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "text/markdown") - _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) - require.NoError(t, err) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetRawReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/markdown") + _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) + require.NoError(t, err) + }), + }), uri: "repo://owner/repo/contents/README.md", handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { return RepositoryResourceContentsHandler(deps, repositoryResourceContentURITemplate) @@ -120,18 +107,15 @@ func Test_repositoryResourceContents(t *testing.T) { }, { name: "successful text content fetch (HEAD)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain") + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetRawReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") - require.Contains(t, r.URL.Path, "pkg/github/actions.go") - _, err := w.Write([]byte("package actions\n\nfunc main() {\n // Sample Go file content\n}\n")) - require.NoError(t, err) - }), - ), - ), + require.Contains(t, r.URL.Path, "pkg/github/actions.go") + _, err := w.Write([]byte("package actions\n\nfunc main() {\n // Sample Go file content\n}\n")) + require.NoError(t, err) + }), + }), uri: "repo://owner/repo/contents/pkg/github/actions.go", handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { return RepositoryResourceContentsHandler(deps, repositoryResourceContentURITemplate) @@ -146,16 +130,13 @@ func Test_repositoryResourceContents(t *testing.T) { }, { name: "successful text content fetch (branch)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByBranchByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "text/markdown") - _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) - require.NoError(t, err) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetRawReposContentsByOwnerByRepoByBranchByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/markdown") + _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) + require.NoError(t, err) + }), + }), uri: "repo://owner/repo/refs/heads/main/contents/README.md", handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { return RepositoryResourceContentsHandler(deps, repositoryResourceBranchContentURITemplate) @@ -170,16 +151,13 @@ func Test_repositoryResourceContents(t *testing.T) { }, { name: "successful text content fetch (tag)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByTagByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "text/markdown") - _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) - require.NoError(t, err) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetRawReposContentsByOwnerByRepoByTagByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/markdown") + _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) + require.NoError(t, err) + }), + }), uri: "repo://owner/repo/refs/tags/v1.0.0/contents/README.md", handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { return RepositoryResourceContentsHandler(deps, repositoryResourceTagContentURITemplate) @@ -194,16 +172,13 @@ func Test_repositoryResourceContents(t *testing.T) { }, { name: "successful text content fetch (sha)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoBySHAByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "text/markdown") - _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) - require.NoError(t, err) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetRawReposContentsByOwnerByRepoBySHAByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/markdown") + _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) + require.NoError(t, err) + }), + }), uri: "repo://owner/repo/sha/abc123/contents/README.md", handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { return RepositoryResourceContentsHandler(deps, repositoryResourceCommitContentURITemplate) @@ -218,24 +193,18 @@ func Test_repositoryResourceContents(t *testing.T) { }, { name: "successful text content fetch (pr)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposPullsByOwnerByRepoByPullNumber, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "application/json") - _, err := w.Write([]byte(`{"head": {"sha": "abc123"}}`)) - require.NoError(t, err) - }), - ), - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoBySHAByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "text/markdown") - _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) - require.NoError(t, err) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsByOwnerByRepoByPullNumber: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, err := w.Write([]byte(`{"head": {"sha": "abc123"}}`)) + require.NoError(t, err) + }), + GetRawReposContentsByOwnerByRepoBySHAByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/markdown") + _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) + require.NoError(t, err) + }), + }), uri: "repo://owner/repo/refs/pull/42/head/contents/README.md", handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { return RepositoryResourceContentsHandler(deps, repositoryResourcePrContentURITemplate) @@ -250,15 +219,12 @@ func Test_repositoryResourceContents(t *testing.T) { }, { name: "content fetch fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), uri: "repo://owner/repo/contents/nonexistent.md", handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { return RepositoryResourceContentsHandler(deps, repositoryResourceContentURITemplate) diff --git a/pkg/github/secret_scanning_test.go b/pkg/github/secret_scanning_test.go index 23ac868c7..ed05d2215 100644 --- a/pkg/github/secret_scanning_test.go +++ b/pkg/github/secret_scanning_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" ) @@ -48,12 +47,9 @@ func Test_GetSecretScanningAlert(t *testing.T) { }{ { name: "successful alert fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposSecretScanningAlertsByOwnerByRepoByAlertNumber, - mockAlert, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposSecretScanningAlertsByOwnerByRepoByAlertNumber: mockResponse(t, http.StatusOK, mockAlert), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -64,15 +60,12 @@ func Test_GetSecretScanningAlert(t *testing.T) { }, { name: "alert fetch fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposSecretScanningAlertsByOwnerByRepoByAlertNumber, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposSecretScanningAlertsByOwnerByRepoByAlertNumber: 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", @@ -170,16 +163,13 @@ func Test_ListSecretScanningAlerts(t *testing.T) { }{ { name: "successful resolved alerts listing", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposSecretScanningAlertsByOwnerByRepo, - expectQueryParams(t, map[string]string{ - "state": "resolved", - }).andThen( - mockResponse(t, http.StatusOK, []*github.SecretScanningAlert{&resolvedAlert}), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposSecretScanningAlertsByOwnerByRepo: expectQueryParams(t, map[string]string{ + "state": "resolved", + }).andThen( + mockResponse(t, http.StatusOK, []*github.SecretScanningAlert{&resolvedAlert}), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -190,14 +180,11 @@ func Test_ListSecretScanningAlerts(t *testing.T) { }, { name: "successful alerts listing", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposSecretScanningAlertsByOwnerByRepo, - expectQueryParams(t, map[string]string{}).andThen( - mockResponse(t, http.StatusOK, []*github.SecretScanningAlert{&resolvedAlert, &openAlert}), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposSecretScanningAlertsByOwnerByRepo: expectQueryParams(t, map[string]string{}).andThen( + mockResponse(t, http.StatusOK, []*github.SecretScanningAlert{&resolvedAlert, &openAlert}), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -207,15 +194,12 @@ func Test_ListSecretScanningAlerts(t *testing.T) { }, { name: "alerts listing fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposSecretScanningAlertsByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnauthorized) - _, _ = w.Write([]byte(`{"message": "Unauthorized access"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposSecretScanningAlertsByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte(`{"message": "Unauthorized access"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", diff --git a/pkg/github/security_advisories_test.go b/pkg/github/security_advisories_test.go index d1e943bd7..bfc4c6985 100644 --- a/pkg/github/security_advisories_test.go +++ b/pkg/github/security_advisories_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" ) @@ -50,12 +49,9 @@ func Test_ListGlobalSecurityAdvisories(t *testing.T) { }{ { name: "successful advisory fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetAdvisories, - []*github.GlobalSecurityAdvisory{mockAdvisory}, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetAdvisories: mockResponse(t, http.StatusOK, []*github.GlobalSecurityAdvisory{mockAdvisory}), + }), requestArgs: map[string]interface{}{ "type": "reviewed", "ecosystem": "npm", @@ -66,15 +62,12 @@ func Test_ListGlobalSecurityAdvisories(t *testing.T) { }, { name: "invalid severity value", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetAdvisories, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message": "Bad Request"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetAdvisories: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message": "Bad Request"}`)) + }), + }), requestArgs: map[string]interface{}{ "type": "reviewed", "severity": "extreme", @@ -84,15 +77,12 @@ func Test_ListGlobalSecurityAdvisories(t *testing.T) { }, { name: "API error handling", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetAdvisories, - 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{ + GetAdvisories: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(`{"message": "Internal Server Error"}`)) + }), + }), requestArgs: map[string]interface{}{}, expectError: true, expectedErrMsg: "failed to list global security advisories", @@ -172,12 +162,9 @@ func Test_GetGlobalSecurityAdvisory(t *testing.T) { }{ { name: "successful advisory fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetAdvisoriesByGhsaId, - mockAdvisory, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetAdvisoriesByGhsaID: mockResponse(t, http.StatusOK, mockAdvisory), + }), requestArgs: map[string]interface{}{ "ghsaId": "GHSA-xxxx-xxxx-xxxx", }, @@ -186,15 +173,12 @@ func Test_GetGlobalSecurityAdvisory(t *testing.T) { }, { name: "invalid ghsaId format", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetAdvisoriesByGhsaId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message": "Bad Request"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetAdvisoriesByGhsaID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message": "Bad Request"}`)) + }), + }), requestArgs: map[string]interface{}{ "ghsaId": "invalid-ghsa-id", }, @@ -203,15 +187,12 @@ func Test_GetGlobalSecurityAdvisory(t *testing.T) { }, { name: "advisory not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetAdvisoriesByGhsaId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetAdvisoriesByGhsaID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), requestArgs: map[string]interface{}{ "ghsaId": "GHSA-xxxx-xxxx-xxxx", }, @@ -272,12 +253,6 @@ func Test_ListRepositorySecurityAdvisories(t *testing.T) { assert.Contains(t, schema.Properties, "state") assert.ElementsMatch(t, schema.Required, []string{"owner", "repo"}) - // Local endpoint pattern for repository security advisories - var GetReposSecurityAdvisoriesByOwnerByRepo = mock.EndpointPattern{ - Pattern: "/repos/{owner}/{repo}/security-advisories", - Method: "GET", - } - // Setup mock advisories for success cases adv1 := &github.SecurityAdvisory{ GHSAID: github.Ptr("GHSA-1111-1111-1111"), @@ -302,17 +277,14 @@ func Test_ListRepositorySecurityAdvisories(t *testing.T) { }{ { name: "successful advisories listing (no filters)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - GetReposSecurityAdvisoriesByOwnerByRepo, - expect(t, expectations{ - path: "/repos/owner/repo/security-advisories", - queryParams: map[string]string{}, - }).andThen( - mockResponse(t, http.StatusOK, []*github.SecurityAdvisory{adv1, adv2}), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposSecurityAdvisoriesByOwnerByRepo: expect(t, expectations{ + path: "/repos/owner/repo/security-advisories", + queryParams: map[string]string{}, + }).andThen( + mockResponse(t, http.StatusOK, []*github.SecurityAdvisory{adv1, adv2}), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -322,21 +294,18 @@ func Test_ListRepositorySecurityAdvisories(t *testing.T) { }, { name: "successful advisories listing with filters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - GetReposSecurityAdvisoriesByOwnerByRepo, - expect(t, expectations{ - path: "/repos/octo/hello-world/security-advisories", - queryParams: map[string]string{ - "direction": "desc", - "sort": "updated", - "state": "published", - }, - }).andThen( - mockResponse(t, http.StatusOK, []*github.SecurityAdvisory{adv1}), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposSecurityAdvisoriesByOwnerByRepo: expect(t, expectations{ + path: "/repos/octo/hello-world/security-advisories", + queryParams: map[string]string{ + "direction": "desc", + "sort": "updated", + "state": "published", + }, + }).andThen( + mockResponse(t, http.StatusOK, []*github.SecurityAdvisory{adv1}), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "octo", "repo": "hello-world", @@ -349,17 +318,14 @@ func Test_ListRepositorySecurityAdvisories(t *testing.T) { }, { name: "advisories listing fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - GetReposSecurityAdvisoriesByOwnerByRepo, - expect(t, expectations{ - path: "/repos/owner/repo/security-advisories", - queryParams: map[string]string{}, - }).andThen( - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "Internal Server Error"}), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposSecurityAdvisoriesByOwnerByRepo: expect(t, expectations{ + path: "/repos/owner/repo/security-advisories", + queryParams: map[string]string{}, + }).andThen( + mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "Internal Server Error"}), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -421,12 +387,6 @@ func Test_ListOrgRepositorySecurityAdvisories(t *testing.T) { assert.Contains(t, schema.Properties, "state") assert.ElementsMatch(t, schema.Required, []string{"org"}) - // Endpoint pattern for org repository security advisories - var GetOrgsSecurityAdvisoriesByOrg = mock.EndpointPattern{ - Pattern: "/orgs/{org}/security-advisories", - Method: "GET", - } - adv1 := &github.SecurityAdvisory{ GHSAID: github.Ptr("GHSA-aaaa-bbbb-cccc"), Summary: github.Ptr("Org repo advisory 1"), @@ -450,17 +410,14 @@ func Test_ListOrgRepositorySecurityAdvisories(t *testing.T) { }{ { name: "successful listing (no filters)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - GetOrgsSecurityAdvisoriesByOrg, - expect(t, expectations{ - path: "/orgs/octo/security-advisories", - queryParams: map[string]string{}, - }).andThen( - mockResponse(t, http.StatusOK, []*github.SecurityAdvisory{adv1, adv2}), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsSecurityAdvisoriesByOrg: expect(t, expectations{ + path: "/orgs/octo/security-advisories", + queryParams: map[string]string{}, + }).andThen( + mockResponse(t, http.StatusOK, []*github.SecurityAdvisory{adv1, adv2}), ), - ), + }), requestArgs: map[string]interface{}{ "org": "octo", }, @@ -469,21 +426,18 @@ func Test_ListOrgRepositorySecurityAdvisories(t *testing.T) { }, { name: "successful listing with filters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - GetOrgsSecurityAdvisoriesByOrg, - expect(t, expectations{ - path: "/orgs/octo/security-advisories", - queryParams: map[string]string{ - "direction": "asc", - "sort": "created", - "state": "triage", - }, - }).andThen( - mockResponse(t, http.StatusOK, []*github.SecurityAdvisory{adv1}), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsSecurityAdvisoriesByOrg: expect(t, expectations{ + path: "/orgs/octo/security-advisories", + queryParams: map[string]string{ + "direction": "asc", + "sort": "created", + "state": "triage", + }, + }).andThen( + mockResponse(t, http.StatusOK, []*github.SecurityAdvisory{adv1}), ), - ), + }), requestArgs: map[string]interface{}{ "org": "octo", "direction": "asc", @@ -495,17 +449,14 @@ func Test_ListOrgRepositorySecurityAdvisories(t *testing.T) { }, { name: "listing fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - GetOrgsSecurityAdvisoriesByOrg, - expect(t, expectations{ - path: "/orgs/octo/security-advisories", - queryParams: map[string]string{}, - }).andThen( - mockResponse(t, http.StatusForbidden, map[string]string{"message": "Forbidden"}), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsSecurityAdvisoriesByOrg: expect(t, expectations{ + path: "/orgs/octo/security-advisories", + queryParams: map[string]string{}, + }).andThen( + mockResponse(t, http.StatusForbidden, map[string]string{"message": "Forbidden"}), ), - ), + }), requestArgs: map[string]interface{}{ "org": "octo", }, diff --git a/pkg/raw/raw_test.go b/pkg/raw/raw_test.go index 242029c8b..4c4aa33b4 100644 --- a/pkg/raw/raw_test.go +++ b/pkg/raw/raw_test.go @@ -1,22 +1,44 @@ package raw import ( + "bytes" "context" + "io" "net/http" "net/url" + "strings" "testing" "github.com/google/go-github/v79/github" - "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/stretchr/testify/require" ) +// mockRawTransport is a custom HTTP transport for testing raw content API +type mockRawTransport struct { + statusCode int + contentType string + body string +} + +func (m *mockRawTransport) RoundTrip(req *http.Request) (*http.Response, error) { + // Create a response with the configured status and body + resp := &http.Response{ + StatusCode: m.statusCode, + Header: make(http.Header), + Body: io.NopCloser(bytes.NewBufferString(m.body)), + Request: req, + } + if m.contentType != "" { + resp.Header.Set("Content-Type", m.contentType) + } + return resp, nil +} + func TestGetRawContent(t *testing.T) { base, _ := url.Parse("https://raw.example.com/") tests := []struct { name string - pattern mock.EndpointPattern opts *ContentOpts owner, repo, path string statusCode int @@ -25,46 +47,51 @@ func TestGetRawContent(t *testing.T) { expectError string }{ { - name: "HEAD fetch success", - pattern: GetRawReposContentsByOwnerByRepoByPath, - opts: nil, - owner: "octocat", repo: "hello", path: "README.md", + name: "HEAD fetch success", + opts: nil, + owner: "octocat", + repo: "hello", + path: "README.md", statusCode: 200, contentType: "text/plain", body: "# Test file", }, { - name: "branch fetch success", - pattern: GetRawReposContentsByOwnerByRepoByBranchByPath, - opts: &ContentOpts{Ref: "refs/heads/main"}, - owner: "octocat", repo: "hello", path: "README.md", + name: "branch fetch success", + opts: &ContentOpts{Ref: "refs/heads/main"}, + owner: "octocat", + repo: "hello", + path: "README.md", statusCode: 200, contentType: "text/plain", body: "# Test file", }, { - name: "tag fetch success", - pattern: GetRawReposContentsByOwnerByRepoByTagByPath, - opts: &ContentOpts{Ref: "refs/tags/v1.0.0"}, - owner: "octocat", repo: "hello", path: "README.md", + name: "tag fetch success", + opts: &ContentOpts{Ref: "refs/tags/v1.0.0"}, + owner: "octocat", + repo: "hello", + path: "README.md", statusCode: 200, contentType: "text/plain", body: "# Test file", }, { - name: "sha fetch success", - pattern: GetRawReposContentsByOwnerByRepoBySHAByPath, - opts: &ContentOpts{SHA: "abc123"}, - owner: "octocat", repo: "hello", path: "README.md", + name: "sha fetch success", + opts: &ContentOpts{SHA: "abc123"}, + owner: "octocat", + repo: "hello", + path: "README.md", statusCode: 200, contentType: "text/plain", body: "# Test file", }, { - name: "not found", - pattern: GetRawReposContentsByOwnerByRepoByPath, - opts: nil, - owner: "octocat", repo: "hello", path: "notfound.txt", + name: "not found", + opts: nil, + owner: "octocat", + repo: "hello", + path: "notfound.txt", statusCode: 404, contentType: "application/json", body: `{"message": "Not Found"}`, @@ -73,29 +100,33 @@ func TestGetRawContent(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - tc.pattern, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", tc.contentType) - w.WriteHeader(tc.statusCode) - _, err := w.Write([]byte(tc.body)) - require.NoError(t, err) - }), - ), - ) + // Create mock HTTP client with custom transport + mockedClient := &http.Client{ + Transport: &mockRawTransport{ + statusCode: tc.statusCode, + contentType: tc.contentType, + body: tc.body, + }, + } ghClient := github.NewClient(mockedClient) client := NewClient(ghClient, base) resp, err := client.GetRawContent(context.Background(), tc.owner, tc.repo, tc.path, tc.opts) defer func() { _ = resp.Body.Close() }() + if tc.expectError != "" { require.Error(t, err) return } require.NoError(t, err) require.Equal(t, tc.statusCode, resp.StatusCode) + + // Verify the URL was constructed correctly + actualURL := client.URLFromOpts(tc.opts, tc.owner, tc.repo, tc.path) + require.True(t, strings.Contains(actualURL, tc.owner)) + require.True(t, strings.Contains(actualURL, tc.repo)) + require.True(t, strings.Contains(actualURL, tc.path)) }) } }