forked from Gitlink/gitlink-cli
remove: project board shortcut (duplicated with issue commands)
CI / test (push) Failing after 2m7s
Details
CI / test (push) Failing after 2m7s
Details
This commit is contained in:
parent
811bae1cd5
commit
59552d9286
|
|
@ -1,294 +0,0 @@
|
|||
package board
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
// v1RepoPath returns the v1 API path prefix.
|
||||
func v1RepoPath(ctx *common.RuntimeContext) string {
|
||||
return fmt.Sprintf("/v1/%s/%s", ctx.Owner, ctx.Repo)
|
||||
}
|
||||
|
||||
// Shortcuts returns project board management shortcuts.
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "view",
|
||||
Description: "Show project board (issues grouped by status)",
|
||||
Flags: []common.Flag{
|
||||
{Name: "state", Short: "s", Usage: "Filter by state: open, closed, all", Default: "open"},
|
||||
{Name: "milestone", Short: "m", Usage: "Milestone ID to filter by"},
|
||||
},
|
||||
Run: runView,
|
||||
},
|
||||
{
|
||||
Name: "columns",
|
||||
Description: "List available board columns (issue statuses)",
|
||||
Run: runColumns,
|
||||
},
|
||||
{
|
||||
Name: "move",
|
||||
Description: "Move an issue to a different status column",
|
||||
Flags: []common.Flag{
|
||||
{Name: "number", Short: "n", Usage: "Issue number", Required: true},
|
||||
{Name: "state", Short: "s", Usage: "Target state: open, in_progress, resolved, closed, or numeric status_id", Required: true},
|
||||
},
|
||||
Run: runMove,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Default known status mapping (fallback if API call fails)
|
||||
var knownStatuses = map[int]string{
|
||||
1: "New",
|
||||
2: "In Progress",
|
||||
3: "Resolved",
|
||||
4: "Reopened",
|
||||
5: "Closed",
|
||||
6: "Rejected",
|
||||
}
|
||||
|
||||
// statusNameToID maps common names to status IDs
|
||||
// Supports both Chinese and English names
|
||||
var statusNameToID = map[string]int{
|
||||
"open": 1,
|
||||
"new": 1,
|
||||
"新增": 1,
|
||||
"in_progress": 2,
|
||||
"inprogress": 2,
|
||||
"wip": 2,
|
||||
"正在解决": 2,
|
||||
"resolved": 3,
|
||||
"已解决": 3,
|
||||
"reopened": 4,
|
||||
"closed": 5,
|
||||
"close": 5,
|
||||
"关闭": 5,
|
||||
"rejected": 6,
|
||||
"拒绝": 6,
|
||||
"feedback": 6,
|
||||
}
|
||||
|
||||
// --- Commands ---
|
||||
|
||||
func runView(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q := url.Values{}
|
||||
q.Set("limit", "100")
|
||||
if s := ctx.Arg("state"); s != "" {
|
||||
q.Set("state", s)
|
||||
}
|
||||
if m := ctx.Arg("milestone"); m != "" {
|
||||
q.Set("fixed_version_id", m)
|
||||
}
|
||||
|
||||
env, err := ctx.CallAPIWithQuery("GET", v1RepoPath(ctx)+"/issues", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse issues and group by status
|
||||
raw, ok := env.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
issuesRaw, ok := raw["issues"].([]interface{})
|
||||
if !ok {
|
||||
// No issues, return empty board
|
||||
return ctx.OutputData(map[string]interface{}{
|
||||
"columns": []map[string]interface{}{},
|
||||
"total": 0,
|
||||
})
|
||||
}
|
||||
|
||||
// Group issues by status_id
|
||||
columns := make(map[int][]map[string]interface{})
|
||||
statusOrder := make([]int, 0)
|
||||
|
||||
for _, rawIssue := range issuesRaw {
|
||||
issue, ok := rawIssue.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract number
|
||||
number := 0
|
||||
if n, ok := issue["project_issues_index"].(float64); ok {
|
||||
number = int(n)
|
||||
} else if n, ok := issue["number"]; ok {
|
||||
if v, ok := n.(float64); ok {
|
||||
number = int(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Extract status_id
|
||||
statusID := 0
|
||||
if s, ok := issue["status_id"]; ok {
|
||||
if v, ok := s.(float64); ok {
|
||||
statusID = int(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Extract subject
|
||||
subject, _ := issue["subject"].(string)
|
||||
|
||||
// Extract assignee
|
||||
assignee := ""
|
||||
if assigned, ok := issue["assigned_to"]; ok {
|
||||
if a, ok := assigned.(map[string]interface{}); ok {
|
||||
if login, ok := a["login"].(string); ok {
|
||||
assignee = login
|
||||
} else if name, ok := a["name"].(string); ok {
|
||||
assignee = name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item := map[string]interface{}{
|
||||
"number": number,
|
||||
"subject": subject,
|
||||
"assignee": assignee,
|
||||
}
|
||||
|
||||
if _, exists := columns[statusID]; !exists {
|
||||
columns[statusID] = make([]map[string]interface{}, 0)
|
||||
statusOrder = append(statusOrder, statusID)
|
||||
}
|
||||
columns[statusID] = append(columns[statusID], item)
|
||||
}
|
||||
|
||||
// Sort status IDs
|
||||
sort.Ints(statusOrder)
|
||||
|
||||
// Build output
|
||||
boardColumns := make([]map[string]interface{}, 0)
|
||||
for _, sid := range statusOrder {
|
||||
name, ok := knownStatuses[sid]
|
||||
if !ok {
|
||||
name = fmt.Sprintf("Status %d", sid)
|
||||
}
|
||||
boardColumns = append(boardColumns, map[string]interface{}{
|
||||
"id": sid,
|
||||
"name": name,
|
||||
"issues": columns[sid],
|
||||
"count": len(columns[sid]),
|
||||
})
|
||||
}
|
||||
|
||||
total := 0
|
||||
for _, col := range boardColumns {
|
||||
total += col["count"].(int)
|
||||
}
|
||||
|
||||
return ctx.OutputData(map[string]interface{}{
|
||||
"columns": boardColumns,
|
||||
"total": total,
|
||||
})
|
||||
}
|
||||
|
||||
func runColumns(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Try fetching from API first
|
||||
env, err := ctx.CallAPI("GET", v1RepoPath(ctx)+"/issue_statues", nil)
|
||||
if err == nil {
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
// Fallback to known statuses
|
||||
type statusInfo struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
statuses := make([]statusInfo, 0)
|
||||
for id, name := range knownStatuses {
|
||||
statuses = append(statuses, statusInfo{ID: id, Name: name})
|
||||
}
|
||||
sort.Slice(statuses, func(i, j int) bool {
|
||||
return statuses[i].ID < statuses[j].ID
|
||||
})
|
||||
|
||||
return ctx.OutputData(map[string]interface{}{
|
||||
"statuses": statuses,
|
||||
})
|
||||
}
|
||||
|
||||
func runMove(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
number, err := ctx.RequireArg("number")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state := ctx.Arg("state")
|
||||
if state == "" {
|
||||
return fmt.Errorf("required flag --state is missing")
|
||||
}
|
||||
|
||||
// Resolve status_id from state string or numeric
|
||||
var statusID int
|
||||
if id, err := strconv.Atoi(state); err == nil {
|
||||
statusID = id
|
||||
} else if id, ok := statusNameToID[strings.ToLower(strings.TrimSpace(state))]; ok {
|
||||
statusID = id
|
||||
} else {
|
||||
return fmt.Errorf("invalid --state %q: use open, in_progress, resolved, closed, or a numeric status_id", state)
|
||||
}
|
||||
|
||||
// Fetch current issue to preserve subject and description
|
||||
current, err := fetchIssue(ctx, number)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch issue: %w", err)
|
||||
}
|
||||
|
||||
// Update the issue status
|
||||
payload := map[string]interface{}{
|
||||
"subject": current.Subject,
|
||||
"description": current.Description,
|
||||
"status_id": statusID,
|
||||
}
|
||||
|
||||
env, err := ctx.CallAPI("PATCH", fmt.Sprintf("%s/issues/%s", v1RepoPath(ctx), number), payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
type existingIssue struct {
|
||||
Subject string
|
||||
Description string
|
||||
}
|
||||
|
||||
func fetchIssue(ctx *common.RuntimeContext, number string) (*existingIssue, error) {
|
||||
getEnv, err := ctx.CallAPI("GET", fmt.Sprintf("%s/issues/%s", v1RepoPath(ctx), number), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, ok := getEnv.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to parse issue data")
|
||||
}
|
||||
subject, _ := data["subject"].(string)
|
||||
description, _ := data["description"].(string)
|
||||
return &existingIssue{
|
||||
Subject: subject,
|
||||
Description: description,
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -1,405 +0,0 @@
|
|||
package board
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/client"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
// --- Test helpers ---
|
||||
|
||||
func runBoardShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
|
||||
t.Helper()
|
||||
shortcut := findBoardShortcut(t, name)
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{
|
||||
HTTP: server.Client(),
|
||||
BaseURL: server.URL,
|
||||
},
|
||||
Owner: "test-owner",
|
||||
Repo: "test-repo",
|
||||
Format: "json",
|
||||
Args: args,
|
||||
}
|
||||
if ctx.Args == nil {
|
||||
ctx.Args = map[string]string{}
|
||||
}
|
||||
return shortcut.Run(ctx)
|
||||
}
|
||||
|
||||
func findBoardShortcut(t *testing.T, name string) *common.Shortcut {
|
||||
t.Helper()
|
||||
for _, shortcut := range Shortcuts() {
|
||||
if shortcut.Name == name {
|
||||
return shortcut
|
||||
}
|
||||
}
|
||||
t.Fatalf("shortcut %q not found", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newBoardTestServer(t *testing.T, handler http.HandlerFunc) *httptest.Server {
|
||||
t.Helper()
|
||||
return httptest.NewServer(handler)
|
||||
}
|
||||
|
||||
func writeJSON(t *testing.T, w http.ResponseWriter, payload interface{}) {
|
||||
t.Helper()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(payload); err != nil {
|
||||
t.Fatalf("failed to write response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertRequest(t *testing.T, r *http.Request, method, path string) {
|
||||
t.Helper()
|
||||
if r.Method != method || r.URL.Path != path {
|
||||
t.Fatalf("got request %s %s, want %s %s", r.Method, r.URL.Path, method, path)
|
||||
}
|
||||
}
|
||||
|
||||
func assertQueryParam(t *testing.T, r *http.Request, key, expected string) {
|
||||
t.Helper()
|
||||
actual := r.URL.Query().Get(key)
|
||||
if actual != expected {
|
||||
t.Fatalf("query param %q = %q, want %q", key, actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests ---
|
||||
|
||||
func TestBoardView(t *testing.T) {
|
||||
server := newBoardTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/v1/test-owner/test-repo/issues.json")
|
||||
assertQueryParam(t, r, "limit", "100")
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"total_count": 3,
|
||||
"issues": []interface{}{
|
||||
map[string]interface{}{
|
||||
"project_issues_index": float64(1),
|
||||
"subject": "Fix login bug",
|
||||
"status_id": float64(1),
|
||||
},
|
||||
map[string]interface{}{
|
||||
"project_issues_index": float64(2),
|
||||
"subject": "Add dark mode",
|
||||
"status_id": float64(2),
|
||||
"assigned_to": map[string]interface{}{
|
||||
"login": "dev1",
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"project_issues_index": float64(3),
|
||||
"subject": "Update docs",
|
||||
"status_id": float64(5),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runBoardShortcut(t, server, "view", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("board view failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardViewWithStateFilter(t *testing.T) {
|
||||
server := newBoardTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/v1/test-owner/test-repo/issues.json")
|
||||
assertQueryParam(t, r, "state", "closed")
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"total_count": 1,
|
||||
"issues": []interface{}{
|
||||
map[string]interface{}{
|
||||
"project_issues_index": float64(3),
|
||||
"subject": "Closed issue",
|
||||
"status_id": float64(5),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runBoardShortcut(t, server, "view", map[string]string{
|
||||
"state": "closed",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("board view with state filter failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardViewWithMilestoneFilter(t *testing.T) {
|
||||
server := newBoardTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/v1/test-owner/test-repo/issues.json")
|
||||
assertQueryParam(t, r, "fixed_version_id", "42")
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"total_count": 0,
|
||||
"issues": []interface{}{},
|
||||
})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runBoardShortcut(t, server, "view", map[string]string{
|
||||
"milestone": "42",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("board view with milestone filter failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardViewEmpty(t *testing.T) {
|
||||
server := newBoardTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"total_count": 0,
|
||||
"issues": []interface{}{},
|
||||
})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runBoardShortcut(t, server, "view", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("board view for empty repo failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardColumns(t *testing.T) {
|
||||
server := newBoardTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/v1/test-owner/test-repo/issue_statues.json")
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"statues": []interface{}{
|
||||
map[string]interface{}{"id": float64(1), "name": "新增"},
|
||||
map[string]interface{}{"id": float64(2), "name": "正在解决"},
|
||||
map[string]interface{}{"id": float64(3), "name": "已解决"},
|
||||
map[string]interface{}{"id": float64(5), "name": "关闭"},
|
||||
},
|
||||
})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runBoardShortcut(t, server, "columns", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("board columns failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardColumnsFallback(t *testing.T) {
|
||||
// Server returns 404, which should trigger the fallback to known statuses
|
||||
server := newBoardTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
writeJSON(t, w, map[string]interface{}{"message": "not found"})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runBoardShortcut(t, server, "columns", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("board columns fallback failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardMove(t *testing.T) {
|
||||
callCount := 0
|
||||
server := newBoardTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
callCount++
|
||||
if callCount == 1 {
|
||||
// First call: fetch current issue
|
||||
assertRequest(t, r, "GET", "/v1/test-owner/test-repo/issues/5.json")
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"subject": "Test issue",
|
||||
"description": "Test description",
|
||||
})
|
||||
return
|
||||
}
|
||||
// Second call: update issue status
|
||||
assertRequest(t, r, "PATCH", "/v1/test-owner/test-repo/issues/5.json")
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("failed to decode payload: %v", err)
|
||||
}
|
||||
if payload["status_id"] != float64(2) {
|
||||
t.Fatalf("status_id = %v, want 2", payload["status_id"])
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{"id": 5, "status_id": 2})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runBoardShortcut(t, server, "move", map[string]string{
|
||||
"number": "5",
|
||||
"state": "in_progress",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("board move failed: %v", err)
|
||||
}
|
||||
if callCount != 2 {
|
||||
t.Fatalf("expected 2 API calls, got %d", callCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardMoveWithChineseState(t *testing.T) {
|
||||
callCount := 0
|
||||
server := newBoardTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
callCount++
|
||||
if callCount == 1 {
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"subject": "Test",
|
||||
"description": "Test",
|
||||
})
|
||||
return
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
json.NewDecoder(r.Body).Decode(&payload)
|
||||
if payload["status_id"] != float64(5) {
|
||||
t.Fatalf("status_id = %v, want 5", payload["status_id"])
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runBoardShortcut(t, server, "move", map[string]string{
|
||||
"number": "1",
|
||||
"state": "关闭",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("board move with Chinese state failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardMoveRejectsMissingNumber(t *testing.T) {
|
||||
server := newBoardTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("should not call API, got: %s %s", r.Method, r.URL.Path)
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runBoardShortcut(t, server, "move", map[string]string{
|
||||
"state": "closed",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected error when --number is missing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardMoveRejectsMissingState(t *testing.T) {
|
||||
server := newBoardTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("should not call API, got: %s %s", r.Method, r.URL.Path)
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runBoardShortcut(t, server, "move", map[string]string{
|
||||
"number": "1",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected error when --state is missing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardMoveRejectsInvalidState(t *testing.T) {
|
||||
server := newBoardTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("should not call API, got: %s %s", r.Method, r.URL.Path)
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runBoardShortcut(t, server, "move", map[string]string{
|
||||
"number": "1",
|
||||
"state": "invalid_state_name",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid state")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardMoveWithNumericState(t *testing.T) {
|
||||
callCount := 0
|
||||
server := newBoardTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
callCount++
|
||||
if callCount == 1 {
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"subject": "Test",
|
||||
"description": "Test",
|
||||
})
|
||||
return
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
json.NewDecoder(r.Body).Decode(&payload)
|
||||
if payload["status_id"] != float64(3) {
|
||||
t.Fatalf("status_id = %v, want 3", payload["status_id"])
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runBoardShortcut(t, server, "move", map[string]string{
|
||||
"number": "1",
|
||||
"state": "3",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("board move with numeric state failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardViewHandlesAPIError(t *testing.T) {
|
||||
server := newBoardTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
writeJSON(t, w, map[string]interface{}{"message": "not found"})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runBoardShortcut(t, server, "view", nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when API returns 404")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardMoveHandlesAPIFetchError(t *testing.T) {
|
||||
server := newBoardTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
writeJSON(t, w, map[string]interface{}{"message": "issue not found"})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runBoardShortcut(t, server, "move", map[string]string{
|
||||
"number": "999",
|
||||
"state": "closed",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected error when issue does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardViewGroupsIssuesCorrectly(t *testing.T) {
|
||||
server := newBoardTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"issues": []interface{}{
|
||||
map[string]interface{}{
|
||||
"project_issues_index": float64(1),
|
||||
"subject": "Bug A",
|
||||
"status_id": float64(1),
|
||||
},
|
||||
map[string]interface{}{
|
||||
"project_issues_index": float64(2),
|
||||
"subject": "Bug B",
|
||||
"status_id": float64(1),
|
||||
},
|
||||
map[string]interface{}{
|
||||
"project_issues_index": float64(3),
|
||||
"subject": "Task A",
|
||||
"status_id": float64(2),
|
||||
},
|
||||
map[string]interface{}{
|
||||
"project_issues_index": float64(4),
|
||||
"subject": "Done A",
|
||||
"status_id": float64(5),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
err := runBoardShortcut(t, server, "view", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("board view grouping failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/attachment"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/board"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/branch"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/ci"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/collaborator"
|
||||
|
|
@ -44,7 +43,6 @@ func RegisterAll(root *cobra.Command) {
|
|||
"ci": ci.Shortcuts(),
|
||||
"webhook": webhook.Shortcuts(),
|
||||
"wiki": wiki.Shortcuts(),
|
||||
"board": board.Shortcuts(),
|
||||
"snippet": snippet.Shortcuts(),
|
||||
"collaborator": collaborator.Shortcuts(),
|
||||
"tag": tag.Shortcuts(),
|
||||
|
|
@ -71,7 +69,6 @@ func RegisterAll(root *cobra.Command) {
|
|||
"ci": "CI/CD operations",
|
||||
"webhook": "Webhook operations",
|
||||
"wiki": "Wiki operations",
|
||||
"board": "Project board (kanban) operations",
|
||||
"snippet": "Code snippet management",
|
||||
"collaborator": "Collaborator management",
|
||||
"tag": "Git tag operations",
|
||||
|
|
|
|||
Loading…
Reference in New Issue