- errorlint: fmt.Errorf %v→%w across all handlers (errors now wrap the underlying SDK/HTTP error for errors.Is/As); errors.Is for context.Canceled in cmd - staticcheck quickfixes: Fprintf over WriteString(Sprintf), tagged switches, omit inferred 'any'; QF1001 resolved by hoisting hex checks into isHex predicates (normalizeColor, validateSHA) - govet: reflect.Ptr → reflect.Pointer in pkg/ptr - errcheck: explicit '_ =' for fs.Parse (ExitOnError) and deferred zap Sync - repair four test assertions the autofix broke (it dropped the 're.Code != X' half of combined type-assert checks, inverting the test); all errors.As sites use 'var re *resource.ResourceError' golangci-lint: 0 issues. staticcheck, vet, tests: clean.
158 lines
5.2 KiB
Go
158 lines
5.2 KiB
Go
package branchprotection
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"codeberg.org/goern/forgejo-mcp/v2/operation/resource"
|
|
"codeberg.org/goern/forgejo-mcp/v2/pkg/forgejo"
|
|
forgejo_sdk "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v3"
|
|
"github.com/mark3labs/mcp-go/mcp"
|
|
)
|
|
|
|
func setupBPResourceServer(t *testing.T, status int, body interface{}) *httptest.Server {
|
|
t.Helper()
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
if body != nil {
|
|
_ = json.NewEncoder(w).Encode(body)
|
|
}
|
|
}))
|
|
client, err := forgejo_sdk.NewClient(srv.URL, forgejo_sdk.SetForgejoVersion("7.0.0"))
|
|
if err != nil {
|
|
t.Fatalf("creating test client: %v", err)
|
|
}
|
|
forgejo.SetClientForTesting(client)
|
|
return srv
|
|
}
|
|
|
|
func readResourceReq(uri string) mcp.ReadResourceRequest {
|
|
return mcp.ReadResourceRequest{Params: mcp.ReadResourceParams{URI: uri}}
|
|
}
|
|
|
|
func resourceText(t *testing.T, contents []mcp.ResourceContents) string {
|
|
t.Helper()
|
|
if len(contents) != 1 {
|
|
t.Fatalf("expected 1 content block, got %d", len(contents))
|
|
}
|
|
tc, ok := contents[0].(mcp.TextResourceContents)
|
|
if !ok {
|
|
t.Fatalf("expected TextResourceContents, got %T", contents[0])
|
|
}
|
|
return tc.Text
|
|
}
|
|
|
|
func TestBranchProtectionsResource_HappyPath(t *testing.T) {
|
|
srv := setupBPResourceServer(t, http.StatusOK, []map[string]interface{}{
|
|
{"rule_name": "main", "branch_name": "main", "required_approvals": 1},
|
|
{"rule_name": "release/*", "branch_name": "release/*"},
|
|
})
|
|
defer srv.Close()
|
|
|
|
contents, err := branchProtectionsResourceHandler(context.Background(),
|
|
readResourceReq("forgejo://repo/goern/forgejo-mcp/branch_protections"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
var payload bpCollectionPayload
|
|
if err := json.Unmarshal([]byte(resourceText(t, contents)), &payload); err != nil {
|
|
t.Fatalf("unmarshal payload: %v", err)
|
|
}
|
|
if payload.TotalCount != 2 || len(payload.BranchProtections) != 2 {
|
|
t.Errorf("expected 2 rules, got total=%d shown=%d", payload.TotalCount, len(payload.BranchProtections))
|
|
}
|
|
if payload.Truncated {
|
|
t.Error("did not expect truncation for 2 rules")
|
|
}
|
|
}
|
|
|
|
func TestBranchProtectionsResource_Truncation(t *testing.T) {
|
|
rules := make([]map[string]interface{}, resource.EmbeddedListCap+1)
|
|
for i := range rules {
|
|
rules[i] = map[string]interface{}{"rule_name": fmt.Sprintf("rule-%d", i), "branch_name": fmt.Sprintf("b-%d", i)}
|
|
}
|
|
srv := setupBPResourceServer(t, http.StatusOK, rules)
|
|
defer srv.Close()
|
|
|
|
contents, err := branchProtectionsResourceHandler(context.Background(),
|
|
readResourceReq("forgejo://repo/goern/forgejo-mcp/branch_protections"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
var payload bpCollectionPayload
|
|
if err := json.Unmarshal([]byte(resourceText(t, contents)), &payload); err != nil {
|
|
t.Fatalf("unmarshal payload: %v", err)
|
|
}
|
|
if !payload.Truncated {
|
|
t.Error("expected truncation when over EmbeddedListCap")
|
|
}
|
|
if len(payload.BranchProtections) != resource.EmbeddedListCap {
|
|
t.Errorf("expected %d shown rules, got %d", resource.EmbeddedListCap, len(payload.BranchProtections))
|
|
}
|
|
if payload.ListTool != "list_branch_protections" {
|
|
t.Errorf("expected list_tool sentinel, got %q", payload.ListTool)
|
|
}
|
|
}
|
|
|
|
func TestBranchProtectionsResource_NotFound(t *testing.T) {
|
|
srv := setupBPResourceServer(t, http.StatusNotFound, map[string]string{"message": "Not Found"})
|
|
defer srv.Close()
|
|
|
|
_, err := branchProtectionsResourceHandler(context.Background(),
|
|
readResourceReq("forgejo://repo/goern/missing/branch_protections"))
|
|
if err == nil {
|
|
t.Fatal("expected error for 404")
|
|
}
|
|
var re *resource.ResourceError
|
|
ok := errors.As(err, &re)
|
|
if !ok {
|
|
t.Fatalf("expected *resource.ResourceError, got %T", err)
|
|
}
|
|
if re.Code != -32003 {
|
|
t.Errorf("expected code -32003 (not found), got %d", re.Code)
|
|
}
|
|
}
|
|
|
|
func TestBranchProtectionResource_HappyPath(t *testing.T) {
|
|
srv := setupBPResourceServer(t, http.StatusOK, map[string]interface{}{
|
|
"rule_name": "main", "branch_name": "main",
|
|
"status_check_contexts": []string{"ci/build"}, "required_approvals": 2,
|
|
})
|
|
defer srv.Close()
|
|
|
|
contents, err := branchProtectionResourceHandler(context.Background(),
|
|
readResourceReq("forgejo://repo/goern/forgejo-mcp/branch_protection/main"))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
var bp forgejo_sdk.BranchProtection
|
|
if err := json.Unmarshal([]byte(resourceText(t, contents)), &bp); err != nil {
|
|
t.Fatalf("unmarshal payload: %v", err)
|
|
}
|
|
if bp.RuleName != "main" || bp.RequiredApprovals != 2 || len(bp.StatusCheckContexts) != 1 {
|
|
t.Errorf("unexpected protection state: %+v", bp)
|
|
}
|
|
}
|
|
|
|
func TestBranchProtectionResource_MalformedURI(t *testing.T) {
|
|
// No server needed: parse fails before any client call.
|
|
_, err := branchProtectionResourceHandler(context.Background(),
|
|
readResourceReq("forgejo://repo/goern/forgejo-mcp/branch_protection"))
|
|
if err == nil {
|
|
t.Fatal("expected error for malformed URI (missing rule)")
|
|
}
|
|
var re *resource.ResourceError
|
|
ok := errors.As(err, &re)
|
|
if !ok {
|
|
t.Fatalf("expected *resource.ResourceError, got %T", err)
|
|
}
|
|
if re.Code != -32602 {
|
|
t.Errorf("expected code -32602 (invalid params), got %d", re.Code)
|
|
}
|
|
}
|