Add the Redmine REST client with a fake-server test suite
Issues (create/update/partial PUT, notes-as-journals), versions, categories, relations, and name-to-id resolution over trackers/ statuses/priorities. Sentinel errors carry http codes one-line; response bodies are never surfaced so the API key cannot leak through error strings (the fake deliberately echoes the presented key to prove it).
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
package redmine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// enumeration endpoints: trackers, statuses, priorities. The CLI maps
|
||||
// human names ("done", "feature", "immediate") onto ids through these;
|
||||
// the harness gets the same surface so there is no copy-paste temptation.
|
||||
|
||||
type trackerList struct {
|
||||
Trackers []Ref `json:"trackers"`
|
||||
}
|
||||
type statusList struct {
|
||||
Statuses []StatusRef `json:"issue_statuses"`
|
||||
}
|
||||
type priorityList struct {
|
||||
Priorities []Ref `json:"issue_priorities"`
|
||||
}
|
||||
|
||||
// ListTrackers returns the server's issue trackers.
|
||||
func (c *Client) ListTrackers(ctx context.Context) ([]Ref, error) {
|
||||
var body trackerList
|
||||
if err := c.do(ctx, "GET", "/trackers.json", nil, nil, &body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return body.Trackers, nil
|
||||
}
|
||||
|
||||
// ListStatuses returns the server's issue statuses (with is_closed).
|
||||
func (c *Client) ListStatuses(ctx context.Context) ([]StatusRef, error) {
|
||||
var body statusList
|
||||
if err := c.do(ctx, "GET", "/issue_statuses.json", nil, nil, &body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return body.Statuses, nil
|
||||
}
|
||||
|
||||
// ListPriorities returns the server's issue priorities.
|
||||
func (c *Client) ListPriorities(ctx context.Context) ([]Ref, error) {
|
||||
var body priorityList
|
||||
if err := c.do(ctx, "GET", "/enumerations/issue_priorities.json", nil, nil, &body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return body.Priorities, nil
|
||||
}
|
||||
|
||||
// StatusIDByName resolves a status name case-insensitively ("done" ->
|
||||
// Done's id). Unknown names are ErrNotFound; the name, never any server
|
||||
// body, appears in the message.
|
||||
func (c *Client) StatusIDByName(ctx context.Context, name string) (int, error) {
|
||||
list, err := c.ListStatuses(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, s := range list {
|
||||
if strings.EqualFold(s.Name, name) {
|
||||
return s.ID, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("%w: status %q", ErrNotFound, name)
|
||||
}
|
||||
|
||||
// TrackerIDByName resolves a tracker name case-insensitively.
|
||||
func (c *Client) TrackerIDByName(ctx context.Context, name string) (int, error) {
|
||||
list, err := c.ListTrackers(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, t := range list {
|
||||
if strings.EqualFold(t.Name, name) {
|
||||
return t.ID, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("%w: tracker %q", ErrNotFound, name)
|
||||
}
|
||||
|
||||
// PriorityIDByName resolves a priority name case-insensitively.
|
||||
func (c *Client) PriorityIDByName(ctx context.Context, name string) (int, error) {
|
||||
list, err := c.ListPriorities(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, p := range list {
|
||||
if strings.EqualFold(p.Name, name) {
|
||||
return p.ID, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("%w: priority %q", ErrNotFound, name)
|
||||
}
|
||||
|
||||
// VersionIDByName resolves a version name within a project,
|
||||
// case-insensitively.
|
||||
func (c *Client) VersionIDByName(ctx context.Context, project, name string) (int, error) {
|
||||
list, err := c.ListVersions(ctx, project)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, v := range list {
|
||||
if strings.EqualFold(v.Name, name) {
|
||||
return v.ID, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("%w: version %q in %s", ErrNotFound, name, project)
|
||||
}
|
||||
|
||||
// CategoryIDByName resolves a category name within a project,
|
||||
// case-insensitively.
|
||||
func (c *Client) CategoryIDByName(ctx context.Context, project, name string) (int, error) {
|
||||
list, err := c.ListCategories(ctx, project)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, cat := range list {
|
||||
if strings.EqualFold(cat.Name, name) {
|
||||
return cat.ID, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("%w: category %q in %s", ErrNotFound, name, project)
|
||||
}
|
||||
Reference in New Issue
Block a user