fix(cli): require resolve support before resolving issues
- commit
- 7474f50f0c035a407b80f6ece604e179f3809336
- author
- brice · · 2026-10-22 18:36 UTC
- committer
- brice · · 2026-10-22 18:41 UTC
- parent
- 74e7324
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +17 -1
M internal/app/cli_issue.go +3 -0
| @@ -478,6 +478,9 @@ func newIssueCloseCmd(env *cliEnv) *cobra.Command { | |||
| 478 | 478 | func newIssueResolveCmd(env *cliEnv) *cobra.Command { | |
| 479 | 479 | return newIssueStateCmd(env, "resolve", "resolved", "Resolve an issue", "Mark an open issue as resolved (fixed). `ermite issue reopen` opens it again. Needs the issue:write scope.", | |
| 480 | 480 | func(ctx context.Context, c *client.Client, repo string, n int64) (*ermite.Issue, error) { | |
| 481 | + | if err := requireFeature(ctx, c, "issues.resolve", "cannot resolve issues (issue resolve)"); err != nil { | |
| 482 | + | return nil, err | |
| 483 | + | } | |
| 481 | 484 | res, err := c.Issues().ResolveIssue(ctx, connect.NewRequest(&ermite.ResolveIssueRequest{Repo: repo, Number: n})) | |
| 482 | 485 | if err != nil { | |
| 483 | 486 | return nil, err | |
| @@ -478,6 +478,9 @@ func newIssueCloseCmd(env *cliEnv) *cobra.Command { | |||||
| 478 | func newIssueResolveCmd(env *cliEnv) *cobra.Command { | 478 | func newIssueResolveCmd(env *cliEnv) *cobra.Command { | ||
| 479 | return newIssueStateCmd(env, "resolve", "resolved", "Resolve an issue", "Mark an open issue as resolved (fixed). `ermite issue reopen` opens it again. Needs the issue:write scope.", | 479 | return newIssueStateCmd(env, "resolve", "resolved", "Resolve an issue", "Mark an open issue as resolved (fixed). `ermite issue reopen` opens it again. Needs the issue:write scope.", | ||
| 480 | func(ctx context.Context, c *client.Client, repo string, n int64) (*ermite.Issue, error) { | 480 | func(ctx context.Context, c *client.Client, repo string, n int64) (*ermite.Issue, error) { | ||
| 481 | + | if err := requireFeature(ctx, c, "issues.resolve", "cannot resolve issues (issue resolve)"); err != nil { | |||
| 482 | + | return nil, err | |||
| 483 | + | } | |||
| 481 | res, err := c.Issues().ResolveIssue(ctx, connect.NewRequest(&ermite.ResolveIssueRequest{Repo: repo, Number: n})) | 484 | res, err := c.Issues().ResolveIssue(ctx, connect.NewRequest(&ermite.ResolveIssueRequest{Repo: repo, Number: n})) | ||
| 482 | if err != nil { | 485 | if err != nil { | ||
| 483 | return nil, err | 486 | return nil, err | ||
M internal/app/cli_issue_test.go +14 -1
| @@ -303,7 +303,9 @@ func TestIssueResolveAndHistory(t *testing.T) { | |||
| 303 | 303 | {Id: "1", Kind: ermite.IssueEventKind_ISSUE_EVENT_KIND_REFERENCED, Issue: 2, MergeRequest: 5, MergeRequestTitle: "Fix", Closes: true, Actor: "owner", CreatedAt: ts("2026-10-01T10:00:00Z")}, | |
| 304 | 304 | {Id: "2", Kind: ermite.IssueEventKind_ISSUE_EVENT_KIND_RESOLVED, Issue: 2, MergeRequest: 5, Actor: "merger", CreatedAt: ts("2026-10-03T10:00:00Z")}, | |
| 305 | 305 | } | |
| 306 | - | f := &fakeAPI{getVersion: withIssues, | |
| 306 | + | f := &fakeAPI{getVersion: func(*ermite.GetVersionRequest) (*ermite.GetVersionResponse, error) { | |
| 307 | + | return &ermite.GetVersionResponse{Features: []string{"issues", "issues.resolve"}}, nil | |
| 308 | + | }, | |
| 307 | 309 | resolveIssue: func(r *ermite.ResolveIssueRequest) (*ermite.ResolveIssueResponse, error) { | |
| 308 | 310 | assert.Equal(t, "alpha", r.GetRepo()) | |
| 309 | 311 | assert.EqualValues(t, 2, r.GetNumber()) | |
| @@ -333,3 +335,14 @@ func TestIssueResolveAndHistory(t *testing.T) { | |||
| 333 | 335 | assert.Contains(t, res.stdout, `"merge_request_title": "Fix"`) | |
| 334 | 336 | assert.Contains(t, res.stdout, `"kind": "resolved"`) | |
| 335 | 337 | } | |
| 338 | + | ||
| 339 | + | func TestIssueResolveRequiresFeature(t *testing.T) { | |
| 340 | + | f := &fakeAPI{getVersion: withIssues, resolveIssue: func(*ermite.ResolveIssueRequest) (*ermite.ResolveIssueResponse, error) { | |
| 341 | + | t.Error("ResolveIssue must not be called") | |
| 342 | + | return nil, errors.New("unexpected ResolveIssue") | |
| 343 | + | }} | |
| 344 | + | res := runCLI(t, envFor(f.serve(t)), "", "issue", "resolve", "alpha", "2") | |
| 345 | + | require.Error(t, res.err) | |
| 346 | + | assert.Contains(t, res.err.Error(), "the server does not support issues.resolve") | |
| 347 | + | assert.Contains(t, res.err.Error(), "cannot resolve issues") | |
| 348 | + | } | |
| @@ -303,7 +303,9 @@ func TestIssueResolveAndHistory(t *testing.T) { | |||||
| 303 | {Id: "1", Kind: ermite.IssueEventKind_ISSUE_EVENT_KIND_REFERENCED, Issue: 2, MergeRequest: 5, MergeRequestTitle: "Fix", Closes: true, Actor: "owner", CreatedAt: ts("2026-10-01T10:00:00Z")}, | 303 | {Id: "1", Kind: ermite.IssueEventKind_ISSUE_EVENT_KIND_REFERENCED, Issue: 2, MergeRequest: 5, MergeRequestTitle: "Fix", Closes: true, Actor: "owner", CreatedAt: ts("2026-10-01T10:00:00Z")}, | ||
| 304 | {Id: "2", Kind: ermite.IssueEventKind_ISSUE_EVENT_KIND_RESOLVED, Issue: 2, MergeRequest: 5, Actor: "merger", CreatedAt: ts("2026-10-03T10:00:00Z")}, | 304 | {Id: "2", Kind: ermite.IssueEventKind_ISSUE_EVENT_KIND_RESOLVED, Issue: 2, MergeRequest: 5, Actor: "merger", CreatedAt: ts("2026-10-03T10:00:00Z")}, | ||
| 305 | } | 305 | } | ||
| 306 | - | f := &fakeAPI{getVersion: withIssues, | 306 | + | f := &fakeAPI{getVersion: func(*ermite.GetVersionRequest) (*ermite.GetVersionResponse, error) { |
| 307 | + | return &ermite.GetVersionResponse{Features: []string{"issues", "issues.resolve"}}, nil | |||
| 308 | + | }, | |||
| 307 | resolveIssue: func(r *ermite.ResolveIssueRequest) (*ermite.ResolveIssueResponse, error) { | 309 | resolveIssue: func(r *ermite.ResolveIssueRequest) (*ermite.ResolveIssueResponse, error) { | ||
| 308 | assert.Equal(t, "alpha", r.GetRepo()) | 310 | assert.Equal(t, "alpha", r.GetRepo()) | ||
| 309 | assert.EqualValues(t, 2, r.GetNumber()) | 311 | assert.EqualValues(t, 2, r.GetNumber()) | ||
| @@ -333,3 +335,14 @@ func TestIssueResolveAndHistory(t *testing.T) { | |||||
| 333 | assert.Contains(t, res.stdout, `"merge_request_title": "Fix"`) | 335 | assert.Contains(t, res.stdout, `"merge_request_title": "Fix"`) | ||
| 334 | assert.Contains(t, res.stdout, `"kind": "resolved"`) | 336 | assert.Contains(t, res.stdout, `"kind": "resolved"`) | ||
| 335 | } | 337 | } | ||
| 338 | + | ||||
| 339 | + | func TestIssueResolveRequiresFeature(t *testing.T) { | |||
| 340 | + | f := &fakeAPI{getVersion: withIssues, resolveIssue: func(*ermite.ResolveIssueRequest) (*ermite.ResolveIssueResponse, error) { | |||
| 341 | + | t.Error("ResolveIssue must not be called") | |||
| 342 | + | return nil, errors.New("unexpected ResolveIssue") | |||
| 343 | + | }} | |||
| 344 | + | res := runCLI(t, envFor(f.serve(t)), "", "issue", "resolve", "alpha", "2") | |||
| 345 | + | require.Error(t, res.err) | |||
| 346 | + | assert.Contains(t, res.err.Error(), "the server does not support issues.resolve") | |||
| 347 | + | assert.Contains(t, res.err.Error(), "cannot resolve issues") | |||
| 348 | + | } | |||