forgejo-mcp/operation/resource/parse_branchprotection_test.go
Christoph Görn 0826172c23
feat: ✨ branch protection management — CRUD tools + forgejo:// resources (uc6)
Implements the branch-protection OpenSpec change (#195). New
operation/branchprotection/ package:

- Tools: list/get/create/edit/delete_branch_protection over
  /repos/{owner}/{repo}/branch_protections.
- list_branch_protections is output-bounded (page/limit; echoes page/limit
  for resumability).
- create/edit round-trip status_check_contexts (comma-separated). edit uses
  PATCH pointer semantics (pkg/ptr.To): only caller-passed fields change;
  unpassed fields serialize as null (leave-unchanged), never false.
- Resources: bounded collection forgejo://repo/{o}/{r}/branch_protections
  (EmbeddedListCap sentinel -> list_branch_protections) and single
  forgejo://repo/{o}/{r}/branch_protection/{rule} (rule may contain slashes).
- ParseBranchProtection(s) added to operation/resource; wired into
  operation.go + cmd/cli.go domain grouping; README tool table updated.
- httptest coverage: list, get (ok/404), create (contexts round-trip;
  missing branch_name -> no call), edit (only-passed-fields null-safety;
  contexts), delete; parser + truncation + error-mapping tests.

Refines the edit scenario in the change spec to match SDK marshaling
(null = leave-unchanged). tasks.md complete.

Closes forgejo-mcp-uc6.
2026-06-02 16:22:24 +02:00

61 lines
2 KiB
Go

package resource
import (
"errors"
"testing"
)
func TestParseBranchProtections(t *testing.T) {
p, err := ParseBranchProtections("forgejo://repo/goern/forgejo-mcp/branch_protections")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if p.Owner != "goern" || p.Repo != "forgejo-mcp" {
t.Errorf("got %+v", p)
}
}
func TestParseBranchProtections_Invalid(t *testing.T) {
for _, uri := range []string{
"forgejo://repo/goern/forgejo-mcp", // missing segment
"forgejo://repo/goern/forgejo-mcp/branch_protection", // singular, no rule
"forgejo://repo/goern/forgejo-mcp/branch_protections/x", // extra segment
"https://repo/goern/forgejo-mcp/branch_protections", // wrong scheme
} {
if _, err := ParseBranchProtections(uri); !errors.Is(err, ErrInvalidParams) {
t.Errorf("ParseBranchProtections(%q): expected ErrInvalidParams, got %v", uri, err)
}
}
}
func TestParseBranchProtection(t *testing.T) {
p, err := ParseBranchProtection("forgejo://repo/goern/forgejo-mcp/branch_protection/main")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if p.Owner != "goern" || p.Repo != "forgejo-mcp" || p.Rule != "main" {
t.Errorf("got %+v", p)
}
}
func TestParseBranchProtection_GlobRuleWithSlash(t *testing.T) {
p, err := ParseBranchProtection("forgejo://repo/goern/forgejo-mcp/branch_protection/release/v1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if p.Rule != "release/v1" {
t.Errorf("expected rule reassembled to 'release/v1', got %q", p.Rule)
}
}
func TestParseBranchProtection_Invalid(t *testing.T) {
for _, uri := range []string{
"forgejo://repo/goern/forgejo-mcp/branch_protection", // no rule
"forgejo://repo/goern/forgejo-mcp/branch_protections", // plural collection
"forgejo://owner/goern/branch_protection/main", // wrong host
} {
if _, err := ParseBranchProtection(uri); !errors.Is(err, ErrInvalidParams) {
t.Errorf("ParseBranchProtection(%q): expected ErrInvalidParams, got %v", uri, err)
}
}
}