1266 lines
39 KiB
Go
1266 lines
39 KiB
Go
package issue
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gitlink-org/gitlink-cli/internal/client"
|
|
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
|
)
|
|
|
|
func runShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
|
|
t.Helper()
|
|
shortcut := findShortcut(t, name)
|
|
ctx := &common.RuntimeContext{
|
|
Client: &client.Client{
|
|
HTTP: server.Client(),
|
|
BaseURL: server.URL,
|
|
},
|
|
Owner: "owner",
|
|
Repo: "repo",
|
|
Format: "json",
|
|
Args: args,
|
|
}
|
|
if ctx.Args == nil {
|
|
ctx.Args = map[string]string{}
|
|
}
|
|
return shortcut.Run(ctx)
|
|
}
|
|
|
|
func findShortcut(t *testing.T, name string) *common.Shortcut {
|
|
t.Helper()
|
|
for _, s := range Shortcuts() {
|
|
if s.Name == name {
|
|
return s
|
|
}
|
|
}
|
|
t.Fatalf("shortcut %q not found", name)
|
|
return nil
|
|
}
|
|
|
|
func newIssueTestServer(t *testing.T, handler http.HandlerFunc) *httptest.Server {
|
|
t.Helper()
|
|
return httptest.NewServer(handler)
|
|
}
|
|
|
|
func writeJSON(t *testing.T, w http.ResponseWriter, v interface{}) {
|
|
t.Helper()
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(v); err != nil {
|
|
t.Fatalf("failed to write response: %v", err)
|
|
}
|
|
}
|
|
|
|
func writeText(t *testing.T, w http.ResponseWriter, status int, text string) {
|
|
t.Helper()
|
|
w.WriteHeader(status)
|
|
if _, err := w.Write([]byte(text)); err != nil {
|
|
t.Fatalf("write response: %v", err)
|
|
}
|
|
}
|
|
|
|
func decodeJSON(t *testing.T, r *http.Request) map[string]interface{} {
|
|
t.Helper()
|
|
var payload map[string]interface{}
|
|
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
|
t.Fatalf("failed to decode body: %v", err)
|
|
}
|
|
return payload
|
|
}
|
|
|
|
func assertEqual(t *testing.T, got interface{}, want interface{}) {
|
|
t.Helper()
|
|
if got != want {
|
|
t.Fatalf("got %v (%T), want %v (%T)", got, got, want, want)
|
|
}
|
|
}
|
|
|
|
func assertNumberSlice(t *testing.T, got interface{}, want []float64) {
|
|
t.Helper()
|
|
values, ok := got.([]interface{})
|
|
if !ok {
|
|
t.Fatalf("got %v (%T), want numeric slice", got, got)
|
|
}
|
|
if len(values) != len(want) {
|
|
t.Fatalf("got %v, want %v", values, want)
|
|
}
|
|
for i, value := range values {
|
|
if value != want[i] {
|
|
t.Fatalf("got %v, want %v", values, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- list ---
|
|
|
|
func TestIssueList(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" {
|
|
t.Fatalf("expected GET, got %s", r.Method)
|
|
}
|
|
if r.URL.Path != "/v1/owner/repo/issues.json" {
|
|
t.Fatalf("unexpected path: %s", r.URL.Path)
|
|
}
|
|
if r.URL.Query().Get("category") != "opened" {
|
|
t.Fatalf("expected category=opened, got %s", r.URL.Query().Get("category"))
|
|
}
|
|
writeJSON(t, w, []interface{}{
|
|
map[string]interface{}{"id": float64(1), "subject": "bug"},
|
|
})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "list", map[string]string{"state": "open", "page": "1", "limit": "20"})
|
|
if err != nil {
|
|
t.Fatalf("list failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIssueListWithFilters(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/v1/owner/repo/issues.json" {
|
|
t.Fatalf("unexpected path: %s", r.URL.Path)
|
|
}
|
|
query := r.URL.Query()
|
|
assertEqual(t, query.Get("category"), "closed")
|
|
assertEqual(t, query.Get("keyword"), "release")
|
|
assertEqual(t, query.Get("participant_category"), "assignedme")
|
|
assertEqual(t, query.Get("author_id"), "10")
|
|
assertEqual(t, query.Get("assigner_id"), "11")
|
|
assertEqual(t, query.Get("milestone_id"), "12")
|
|
assertEqual(t, query.Get("status_id"), "5")
|
|
assertEqual(t, query.Get("issue_tag_ids"), "1,2")
|
|
assertEqual(t, query.Get("sort_by"), "issues.updated_on")
|
|
assertEqual(t, query.Get("sort_direction"), "desc")
|
|
writeJSON(t, w, map[string]interface{}{"issues": []interface{}{}})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "list", map[string]string{
|
|
"state": "closed",
|
|
"keyword": "release",
|
|
"participant": "assignedme",
|
|
"author-id": "10",
|
|
"assignee-id": "11",
|
|
"milestone-id": "12",
|
|
"status-id": "5",
|
|
"tag-ids": "1,2",
|
|
"sort-by": "issues.updated_on",
|
|
"sort-direction": "desc",
|
|
"page": "2",
|
|
"limit": "50",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("list with filters failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIssueListStateAll(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
assertEqual(t, r.URL.Query().Get("category"), "all")
|
|
writeJSON(t, w, map[string]interface{}{"issues": []interface{}{}})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "list", map[string]string{"state": "all", "page": "1", "limit": "20"})
|
|
if err != nil {
|
|
t.Fatalf("list all failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// --- create ---
|
|
|
|
func TestIssueCreate(t *testing.T) {
|
|
var payload map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "POST" {
|
|
t.Fatalf("expected POST, got %s", r.Method)
|
|
}
|
|
if r.URL.Path != "/v1/owner/repo/issues.json" {
|
|
t.Fatalf("unexpected path: %s", r.URL.Path)
|
|
}
|
|
payload = decodeJSON(t, r)
|
|
writeJSON(t, w, map[string]interface{}{"id": float64(1), "subject": "bug"})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "create", map[string]string{
|
|
"title": "bug: crash",
|
|
"body": "description",
|
|
"assignee": "alice",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create failed: %v", err)
|
|
}
|
|
assertEqual(t, payload["subject"], "bug: crash")
|
|
assertEqual(t, payload["status_id"], float64(1))
|
|
assertEqual(t, payload["assigned_to_id"], "alice")
|
|
}
|
|
|
|
func TestIssueCreateSupportsMetadataFields(t *testing.T) {
|
|
var createPayload map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "POST" || r.URL.Path != "/v1/owner/repo/issues.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
createPayload = decodeJSON(t, r)
|
|
writeJSON(t, w, createPayload)
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "create", map[string]string{
|
|
"title": "New issue",
|
|
"body": "With metadata",
|
|
"priority-id": "3",
|
|
"tag-ids": "4,5",
|
|
"assigner-ids": "7,8",
|
|
"branch": "feature/metadata",
|
|
"start-date": "2026-05-01",
|
|
"due-date": "2026-05-31",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create shortcut failed: %v", err)
|
|
}
|
|
|
|
assertEqual(t, createPayload["subject"], "New issue")
|
|
assertEqual(t, createPayload["description"], "With metadata")
|
|
assertEqual(t, createPayload["priority_id"], float64(3))
|
|
assertNumberSlice(t, createPayload["issue_tag_ids"], []float64{4, 5})
|
|
assertNumberSlice(t, createPayload["assigner_ids"], []float64{7, 8})
|
|
assertEqual(t, createPayload["branch_name"], "feature/metadata")
|
|
assertEqual(t, createPayload["start_date"], "2026-05-01")
|
|
assertEqual(t, createPayload["due_date"], "2026-05-31")
|
|
}
|
|
|
|
func TestIssueCreateMissingTitle(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatal("no API call expected")
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "create", map[string]string{})
|
|
if err == nil {
|
|
t.Fatal("expected error for missing title")
|
|
}
|
|
}
|
|
|
|
// --- view/id alias ---
|
|
|
|
func TestIssueView(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" {
|
|
t.Fatalf("expected GET, got %s", r.Method)
|
|
}
|
|
if r.URL.Path != "/v1/owner/repo/issues/42.json" {
|
|
t.Fatalf("unexpected path: %s", r.URL.Path)
|
|
}
|
|
writeJSON(t, w, map[string]interface{}{"id": float64(42), "subject": "bug"})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "view", map[string]string{"number": "42"})
|
|
if err != nil {
|
|
t.Fatalf("view failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIssueViewMissingNumber(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatal("no API call expected")
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "view", map[string]string{})
|
|
if err == nil {
|
|
t.Fatal("expected error for missing number")
|
|
}
|
|
}
|
|
|
|
func TestIssueViewAcceptsIDAlias(t *testing.T) {
|
|
var requestedPath string
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
requestedPath = r.URL.Path
|
|
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/issues/42.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"project_issues_index": 42,
|
|
"subject": "Issue from web URL",
|
|
})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "view", map[string]string{"id": "42"})
|
|
if err != nil {
|
|
t.Fatalf("view shortcut failed: %v", err)
|
|
}
|
|
assertEqual(t, requestedPath, "/v1/owner/repo/issues/42.json")
|
|
}
|
|
|
|
func TestIssueNumberTakesPrecedenceOverIDAlias(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/issues/42.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
writeJSON(t, w, map[string]interface{}{"subject": "Existing title"})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "view", map[string]string{
|
|
"number": "42",
|
|
"id": "99",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("view shortcut failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// --- close ---
|
|
|
|
func TestIssueClose(t *testing.T) {
|
|
var patchPayload map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"id": float64(42),
|
|
"subject": "Existing title",
|
|
"description": "Existing description",
|
|
})
|
|
case r.Method == "PATCH" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
patchPayload = decodeJSON(t, r)
|
|
writeJSON(t, w, patchPayload)
|
|
default:
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "close", map[string]string{"number": "42"})
|
|
if err != nil {
|
|
t.Fatalf("close failed: %v", err)
|
|
}
|
|
assertEqual(t, patchPayload["subject"], "Existing title")
|
|
assertEqual(t, patchPayload["description"], "Existing description")
|
|
assertEqual(t, patchPayload["status_id"], float64(5))
|
|
}
|
|
|
|
func TestIssueCloseAcceptsIDAlias(t *testing.T) {
|
|
var updatePayload map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"subject": "Existing title",
|
|
"description": "Existing description",
|
|
})
|
|
case r.Method == "PATCH" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
updatePayload = decodeJSON(t, r)
|
|
writeJSON(t, w, updatePayload)
|
|
default:
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "close", map[string]string{"id": "42"})
|
|
if err != nil {
|
|
t.Fatalf("close shortcut failed: %v", err)
|
|
}
|
|
assertEqual(t, updatePayload["status_id"], float64(5))
|
|
}
|
|
|
|
func TestIssueClosePreservesCurrentMetadata(t *testing.T) {
|
|
var updatePayload map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"subject": "Existing title",
|
|
"priority": map[string]interface{}{"id": 3},
|
|
"tags": []map[string]interface{}{
|
|
{"id": 4},
|
|
},
|
|
})
|
|
case r.Method == "PATCH" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
updatePayload = decodeJSON(t, r)
|
|
writeJSON(t, w, updatePayload)
|
|
default:
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "close", map[string]string{"number": "42"})
|
|
if err != nil {
|
|
t.Fatalf("close shortcut failed: %v", err)
|
|
}
|
|
assertEqual(t, updatePayload["subject"], "Existing title")
|
|
assertEqual(t, updatePayload["status_id"], float64(5))
|
|
assertEqual(t, updatePayload["priority_id"], float64(3))
|
|
assertNumberSlice(t, updatePayload["issue_tag_ids"], []float64{4})
|
|
}
|
|
|
|
func TestIssueCloseFetchFails(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
writeJSON(t, w, map[string]interface{}{"error": "not found"})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "close", map[string]string{"number": "999"})
|
|
if err == nil {
|
|
t.Fatal("expected error when issue not found")
|
|
}
|
|
}
|
|
|
|
// --- update ---
|
|
|
|
func TestIssueUpdateTitle(t *testing.T) {
|
|
var patchPayload map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"id": float64(42),
|
|
"subject": "Existing title",
|
|
"description": "Existing description",
|
|
})
|
|
case r.Method == "PATCH" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
patchPayload = decodeJSON(t, r)
|
|
writeJSON(t, w, patchPayload)
|
|
default:
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "update", map[string]string{"number": "42", "title": "New title", "state": "closed"})
|
|
if err != nil {
|
|
t.Fatalf("update failed: %v", err)
|
|
}
|
|
assertEqual(t, patchPayload["subject"], "New title")
|
|
assertEqual(t, patchPayload["description"], "Existing description")
|
|
assertEqual(t, patchPayload["status_id"], float64(5))
|
|
}
|
|
|
|
func TestIssueUpdateDescription(t *testing.T) {
|
|
var patchPayload map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"id": float64(42),
|
|
"subject": "Existing title",
|
|
"description": "Existing description",
|
|
})
|
|
case r.Method == "PATCH" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
patchPayload = decodeJSON(t, r)
|
|
writeJSON(t, w, patchPayload)
|
|
default:
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "update", map[string]string{"number": "42", "body": "New description"})
|
|
if err != nil {
|
|
t.Fatalf("update failed: %v", err)
|
|
}
|
|
assertEqual(t, patchPayload["subject"], "Existing title")
|
|
assertEqual(t, patchPayload["description"], "New description")
|
|
}
|
|
|
|
func TestIssueUpdateNumericState(t *testing.T) {
|
|
var patchPayload map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"id": float64(42),
|
|
"subject": "bug",
|
|
"description": "desc",
|
|
})
|
|
case r.Method == "PATCH" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
patchPayload = decodeJSON(t, r)
|
|
writeJSON(t, w, map[string]interface{}{"id": float64(42)})
|
|
default:
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "update", map[string]string{"number": "42", "state": "3"})
|
|
if err != nil {
|
|
t.Fatalf("update numeric state failed: %v", err)
|
|
}
|
|
assertEqual(t, patchPayload["status_id"], float64(3))
|
|
}
|
|
|
|
func TestIssueUpdateAcceptsIDAlias(t *testing.T) {
|
|
var updatePayload map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"subject": "Existing title",
|
|
"description": "Existing description",
|
|
})
|
|
case r.Method == "PATCH" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
updatePayload = decodeJSON(t, r)
|
|
writeJSON(t, w, updatePayload)
|
|
default:
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "update", map[string]string{
|
|
"id": "42",
|
|
"title": "New title",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("update shortcut failed: %v", err)
|
|
}
|
|
assertEqual(t, updatePayload["subject"], "New title")
|
|
assertEqual(t, updatePayload["description"], "Existing description")
|
|
}
|
|
|
|
func TestIssueUpdatePreservesCurrentMetadata(t *testing.T) {
|
|
var updatePayload map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"subject": "Existing title",
|
|
"description": "Existing description",
|
|
"status": map[string]interface{}{"id": 1},
|
|
"priority": map[string]interface{}{"id": 2},
|
|
"tags": []map[string]interface{}{
|
|
{"id": 7},
|
|
{"id": 8},
|
|
},
|
|
"assigners": []map[string]interface{}{
|
|
{"id": 9},
|
|
},
|
|
"branch_name": "main",
|
|
"start_date": "2026-05-01",
|
|
"due_date": "2026-05-31",
|
|
})
|
|
case r.Method == "PATCH" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
updatePayload = decodeJSON(t, r)
|
|
writeJSON(t, w, updatePayload)
|
|
default:
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "update", map[string]string{
|
|
"number": "42",
|
|
"title": "New title",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("update shortcut failed: %v", err)
|
|
}
|
|
assertEqual(t, updatePayload["subject"], "New title")
|
|
assertEqual(t, updatePayload["description"], "Existing description")
|
|
assertEqual(t, updatePayload["status_id"], float64(1))
|
|
assertEqual(t, updatePayload["priority_id"], float64(2))
|
|
assertNumberSlice(t, updatePayload["issue_tag_ids"], []float64{7, 8})
|
|
assertNumberSlice(t, updatePayload["assigner_ids"], []float64{9})
|
|
assertEqual(t, updatePayload["branch_name"], "main")
|
|
assertEqual(t, updatePayload["start_date"], "2026-05-01")
|
|
assertEqual(t, updatePayload["due_date"], "2026-05-31")
|
|
}
|
|
|
|
func TestIssueUpdateSupportsMetadataFields(t *testing.T) {
|
|
var updatePayload map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"subject": "Existing title",
|
|
"description": "Existing description",
|
|
})
|
|
case r.Method == "PATCH" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
updatePayload = decodeJSON(t, r)
|
|
writeJSON(t, w, updatePayload)
|
|
default:
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "update", map[string]string{
|
|
"number": "42",
|
|
"priority-id": "4",
|
|
"tag-ids": "6,7",
|
|
"assigner-ids": "8",
|
|
"branch": "bugfix/metadata",
|
|
"start-date": "2026-06-01",
|
|
"due-date": "2026-06-15",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("update shortcut failed: %v", err)
|
|
}
|
|
assertEqual(t, updatePayload["subject"], "Existing title")
|
|
assertEqual(t, updatePayload["description"], "Existing description")
|
|
assertEqual(t, updatePayload["priority_id"], float64(4))
|
|
assertNumberSlice(t, updatePayload["issue_tag_ids"], []float64{6, 7})
|
|
assertNumberSlice(t, updatePayload["assigner_ids"], []float64{8})
|
|
assertEqual(t, updatePayload["branch_name"], "bugfix/metadata")
|
|
assertEqual(t, updatePayload["start_date"], "2026-06-01")
|
|
assertEqual(t, updatePayload["due_date"], "2026-06-15")
|
|
}
|
|
|
|
func TestIssueUpdateInvalidState(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"id": float64(42),
|
|
"subject": "bug",
|
|
"description": "desc",
|
|
})
|
|
default:
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "update", map[string]string{"number": "42", "state": "invalid"})
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid state")
|
|
}
|
|
}
|
|
|
|
func TestIssueUpdateNoChanges(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatal("no API call expected")
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "update", map[string]string{"number": "42"})
|
|
if err == nil {
|
|
t.Fatal("expected error when no changes specified")
|
|
}
|
|
}
|
|
|
|
func TestIssueRejectsInvalidMetadataIDs(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatalf("invalid metadata should not call API, got %s %s", r.Method, r.URL.Path)
|
|
})
|
|
defer server.Close()
|
|
|
|
cases := []struct {
|
|
name string
|
|
args map[string]string
|
|
}{
|
|
{name: "bad priority", args: map[string]string{"title": "x", "priority-id": "abc"}},
|
|
{name: "empty tag", args: map[string]string{"title": "x", "tag-ids": "1,,2"}},
|
|
{name: "label conflicts with tag ids", args: map[string]string{"title": "x", "label": "1", "tag-ids": "2"}},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if err := runShortcut(t, server, "create", tc.args); err == nil {
|
|
t.Fatal("expected metadata validation error")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// --- comment ---
|
|
|
|
func TestIssueComment(t *testing.T) {
|
|
var payload map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "POST" {
|
|
t.Fatalf("expected POST, got %s", r.Method)
|
|
}
|
|
if r.URL.Path != "/v1/owner/repo/issues/42/journals.json" {
|
|
t.Fatalf("unexpected path: %s", r.URL.Path)
|
|
}
|
|
payload = decodeJSON(t, r)
|
|
writeJSON(t, w, map[string]interface{}{"id": float64(1), "message": "ok"})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "comment", map[string]string{"number": "42", "body": "test comment"})
|
|
if err != nil {
|
|
t.Fatalf("comment failed: %v", err)
|
|
}
|
|
assertEqual(t, payload["notes"], "test comment")
|
|
}
|
|
|
|
func TestIssueCommentAcceptsIDAlias(t *testing.T) {
|
|
var commentPayload map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "POST" || r.URL.Path != "/v1/owner/repo/issues/42/journals.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
commentPayload = decodeJSON(t, r)
|
|
writeJSON(t, w, commentPayload)
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "comment", map[string]string{
|
|
"id": "42",
|
|
"body": "Fixed",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("comment shortcut failed: %v", err)
|
|
}
|
|
assertEqual(t, commentPayload["notes"], "Fixed")
|
|
}
|
|
|
|
func TestIssueCommentMissingBody(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatal("no API call expected")
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "comment", map[string]string{"number": "42"})
|
|
if err == nil {
|
|
t.Fatal("expected error for missing body")
|
|
}
|
|
}
|
|
|
|
func TestIssueNumberOrIDIsRequired(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
})
|
|
defer server.Close()
|
|
|
|
cases := []struct {
|
|
name string
|
|
args map[string]string
|
|
}{
|
|
{name: "view", args: map[string]string{}},
|
|
{name: "close", args: map[string]string{}},
|
|
{name: "update", args: map[string]string{"title": "New title"}},
|
|
{name: "comment", args: map[string]string{"body": "Fixed"}},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := runShortcut(t, server, tc.name, tc.args)
|
|
if err == nil {
|
|
t.Fatal("expected missing issue number error")
|
|
}
|
|
if !strings.Contains(err.Error(), "--number") || !strings.Contains(err.Error(), "--id") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// --- batch-close ---
|
|
|
|
func TestBatchClosePreservesCurrentDescription(t *testing.T) {
|
|
var updatePayload map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"subject": "Existing title",
|
|
"description": "Existing description",
|
|
})
|
|
case r.Method == "PATCH" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
updatePayload = decodeJSON(t, r)
|
|
writeJSON(t, w, updatePayload)
|
|
default:
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "batch-close", map[string]string{
|
|
"numbers": "42",
|
|
"dry-run": "false",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch-close shortcut failed: %v", err)
|
|
}
|
|
assertEqual(t, updatePayload["subject"], "Existing title")
|
|
assertEqual(t, updatePayload["description"], "Existing description")
|
|
assertEqual(t, updatePayload["status_id"], float64(5))
|
|
}
|
|
|
|
func TestBatchCloseDryRun(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatal("no API call expected in dry-run mode")
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "batch-close", map[string]string{
|
|
"numbers": "1, 2, 3",
|
|
"dry-run": "true",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch-close dry-run failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBatchCloseNoNumbers(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatal("no API call expected")
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "batch-close", map[string]string{})
|
|
if err == nil {
|
|
t.Fatal("expected error when no issue numbers provided")
|
|
}
|
|
}
|
|
|
|
func TestBatchCloseFetchFails(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
writeText(t, w, http.StatusNotFound, "not found")
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "batch-close", map[string]string{"numbers": "99"})
|
|
if err == nil {
|
|
t.Fatal("expected error when fetch fails")
|
|
}
|
|
}
|
|
|
|
func TestBatchCloseWithFailedClose(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/1.json":
|
|
writeJSON(t, w, map[string]interface{}{"subject": "Issue 1", "description": "desc1"})
|
|
case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/2.json":
|
|
writeJSON(t, w, map[string]interface{}{"subject": "Issue 2", "description": "desc2"})
|
|
case r.Method == "PATCH" && r.URL.Path == "/v1/owner/repo/issues/1.json":
|
|
writeJSON(t, w, map[string]interface{}{"subject": "Issue 1", "description": "desc1", "status_id": float64(5)})
|
|
case r.Method == "PATCH" && r.URL.Path == "/v1/owner/repo/issues/2.json":
|
|
writeText(t, w, http.StatusInternalServerError, "server error")
|
|
default:
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "batch-close", map[string]string{"numbers": "1, 2"})
|
|
if err == nil {
|
|
t.Fatal("expected error when some issues fail to close")
|
|
}
|
|
}
|
|
|
|
// --- issue users ---
|
|
|
|
func TestIssueAssignersShortcutWithKeyword(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/issue_assigners.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
assertEqual(t, r.URL.Query().Get("keyword"), "alice")
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"total_count": 1,
|
|
"assigners": []map[string]interface{}{
|
|
{"id": 7, "name": "Alice", "login": "alice"},
|
|
},
|
|
})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "assigners", map[string]string{"keyword": "alice"})
|
|
if err != nil {
|
|
t.Fatalf("assigners shortcut failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIssueAuthorsShortcutWithKeyword(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/issue_authors.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
assertEqual(t, r.URL.Query().Get("keyword"), "bob")
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"total_count": 1,
|
|
"authors": []map[string]interface{}{
|
|
{"id": 8, "name": "Bob", "login": "bob"},
|
|
},
|
|
})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "authors", map[string]string{"keyword": "bob"})
|
|
if err != nil {
|
|
t.Fatalf("authors shortcut failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// --- metadata lookup shortcuts ---
|
|
|
|
func TestIssuePrioritiesShortcut(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/issue_priorities.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
assertEqual(t, r.URL.Query().Get("keyword"), "normal")
|
|
writeJSON(t, w, []map[string]interface{}{
|
|
{"id": 2, "name": "Normal"},
|
|
})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "priorities", map[string]string{"keyword": "normal"})
|
|
if err != nil {
|
|
t.Fatalf("priorities shortcut failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIssueTagsShortcutWithFilters(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/issue_tags.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
query := r.URL.Query()
|
|
assertEqual(t, query.Get("keyword"), "bug")
|
|
assertEqual(t, query.Get("only_name"), "true")
|
|
assertEqual(t, query.Get("order_by"), "issues_count")
|
|
assertEqual(t, query.Get("order_direction"), "desc")
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"total_count": 1,
|
|
"issue_tags": []map[string]interface{}{
|
|
{"id": 3, "name": "bug"},
|
|
},
|
|
})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "tags", map[string]string{
|
|
"keyword": "bug",
|
|
"only-name": "true",
|
|
"order-by": "issues_count",
|
|
"order-direction": "desc",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("tags shortcut failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIssueStatusesShortcut(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/issue_statues.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
assertEqual(t, r.URL.Query().Get("page"), "2")
|
|
assertEqual(t, r.URL.Query().Get("limit"), "10")
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"total_count": 1,
|
|
"statues": []map[string]interface{}{
|
|
{"id": 1, "name": "Open"},
|
|
},
|
|
})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "statuses", map[string]string{"page": "2", "limit": "10"})
|
|
if err != nil {
|
|
t.Fatalf("statuses shortcut failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// --- HTTP error paths ---
|
|
|
|
func TestIssueListHTTPError(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
writeText(t, w, http.StatusInternalServerError, "server error")
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "list", map[string]string{"page": "1", "limit": "20"})
|
|
if err == nil {
|
|
t.Fatal("expected error for HTTP 500")
|
|
}
|
|
}
|
|
|
|
func TestIssueCreateHTTPError(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
writeText(t, w, http.StatusInternalServerError, "server error")
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "create", map[string]string{"title": "test"})
|
|
if err == nil {
|
|
t.Fatal("expected error for HTTP 500")
|
|
}
|
|
}
|
|
|
|
func TestIssueViewHTTPError(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
writeText(t, w, http.StatusInternalServerError, "server error")
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "view", map[string]string{"number": "42"})
|
|
if err == nil {
|
|
t.Fatal("expected error for HTTP 500")
|
|
}
|
|
}
|
|
|
|
func TestIssueCommentHTTPError(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
writeText(t, w, http.StatusInternalServerError, "server error")
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "comment", map[string]string{"number": "42", "body": "test"})
|
|
if err == nil {
|
|
t.Fatal("expected error for HTTP 500")
|
|
}
|
|
}
|
|
|
|
func TestIssueUpdateHTTPError(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"id": float64(42), "subject": "bug", "description": "desc",
|
|
})
|
|
case r.Method == "PATCH" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
writeText(t, w, http.StatusInternalServerError, "server error")
|
|
default:
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "update", map[string]string{"number": "42", "title": "new"})
|
|
if err == nil {
|
|
t.Fatal("expected error for PATCH HTTP 500")
|
|
}
|
|
}
|
|
|
|
func TestIssueCloseHTTPError(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"id": float64(42), "subject": "bug", "description": "desc",
|
|
})
|
|
case r.Method == "PATCH" && r.URL.Path == "/v1/owner/repo/issues/42.json":
|
|
writeText(t, w, http.StatusInternalServerError, "server error")
|
|
default:
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "close", map[string]string{"number": "42"})
|
|
if err == nil {
|
|
t.Fatal("expected error for PATCH HTTP 500")
|
|
}
|
|
}
|
|
|
|
func TestFetchExistingIssueBadData(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
writeJSON(t, w, "not a map")
|
|
})
|
|
defer server.Close()
|
|
|
|
ctx := &common.RuntimeContext{
|
|
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
|
Owner: "owner",
|
|
Repo: "repo",
|
|
}
|
|
_, err := fetchExistingIssue(ctx, "1")
|
|
if err == nil {
|
|
t.Fatal("expected error for non-map response")
|
|
}
|
|
}
|
|
|
|
func TestFetchExistingIssueNoSubject(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
writeJSON(t, w, map[string]interface{}{"id": float64(1)})
|
|
})
|
|
defer server.Close()
|
|
|
|
ctx := &common.RuntimeContext{
|
|
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
|
Owner: "owner",
|
|
Repo: "repo",
|
|
}
|
|
_, err := fetchExistingIssue(ctx, "1")
|
|
if err == nil {
|
|
t.Fatal("expected error for missing subject")
|
|
}
|
|
}
|
|
|
|
// --- normalizeIssueStatus ---
|
|
|
|
func TestNormalizeIssueStatus(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
want interface{}
|
|
wantErr bool
|
|
}{
|
|
{"open", 1, false},
|
|
{"OPEN", 1, false},
|
|
{" open ", 1, false},
|
|
{"closed", 5, false},
|
|
{"CLOSED", 5, false},
|
|
{"0", 0, false},
|
|
{"10", 10, false},
|
|
{"invalid", nil, true},
|
|
{"", nil, true},
|
|
}
|
|
for _, tt := range tests {
|
|
got, err := normalizeIssueStatus(tt.input)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Errorf("normalizeIssueStatus(%q) expected error", tt.input)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Errorf("normalizeIssueStatus(%q) error: %v", tt.input, err)
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("normalizeIssueStatus(%q) = %v, want %v", tt.input, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIssueCommentsListsJournalsWithQuery(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/issues/42/journals.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
q := r.URL.Query()
|
|
assertEqual(t, q.Get("page"), "2")
|
|
assertEqual(t, q.Get("limit"), "5")
|
|
assertEqual(t, q.Get("category"), "comment")
|
|
assertEqual(t, q.Get("keyword"), "hello")
|
|
writeJSON(t, w, map[string]interface{}{"total_count": float64(0), "journals": []interface{}{}})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "comments", map[string]string{
|
|
"number": "42", "page": "2", "limit": "5", "category": "comment", "keyword": "hello",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("comments failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIssueCommentEditPatchesJournal(t *testing.T) {
|
|
var payload map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "PATCH" || r.URL.Path != "/v1/owner/repo/issues/42/journals/99.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
payload = decodeJSON(t, r)
|
|
writeJSON(t, w, map[string]interface{}{"id": float64(99)})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "comment-edit", map[string]string{
|
|
"number": "42", "comment-id": "99", "body": "updated",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("comment-edit failed: %v", err)
|
|
}
|
|
assertEqual(t, payload["notes"], "updated")
|
|
}
|
|
|
|
func TestIssueCommentDeleteUsesJournalPath(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "DELETE" || r.URL.Path != "/v1/owner/repo/issues/42/journals/99.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
writeJSON(t, w, map[string]interface{}{"status": float64(0)})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "comment-delete", map[string]string{"number": "42", "comment-id": "99"})
|
|
if err != nil {
|
|
t.Fatalf("comment-delete failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIssueCommentEditRejectsNonIntegerCommentID(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "comment-edit", map[string]string{
|
|
"number": "42", "comment-id": "abc", "body": "x",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error for non-integer --comment-id")
|
|
}
|
|
}
|
|
|
|
func TestIssueCommentDeleteRequiresCommentID(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "comment-delete", map[string]string{"number": "42"})
|
|
if err == nil {
|
|
t.Fatal("expected error when --comment-id is missing")
|
|
}
|
|
}
|
|
|
|
func TestIssueCommentReplyToSetsParentID(t *testing.T) {
|
|
var payload map[string]interface{}
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "POST" || r.URL.Path != "/v1/owner/repo/issues/7/journals.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
payload = decodeJSON(t, r)
|
|
writeJSON(t, w, map[string]interface{}{"id": float64(101)})
|
|
}))
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "comment", map[string]string{
|
|
"number": "7", "body": "a reply", "reply-to": "99",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("comment failed: %v", err)
|
|
}
|
|
assertEqual(t, payload["notes"], "a reply")
|
|
assertEqual(t, payload["parent_id"], float64(99))
|
|
assertEqual(t, payload["reply_id"], float64(99))
|
|
}
|
|
|
|
func TestIssueCommentRepliesUsesChildrenJournalsPath(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/issues/7/journals/99/children_journals.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
writeJSON(t, w, map[string]interface{}{"total_count": float64(0), "journals": []interface{}{}})
|
|
}))
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "comment-replies", map[string]string{
|
|
"number": "7", "comment-id": "99",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("comment-replies failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIssueCommentRepliesRejectsNonIntegerCommentID(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}))
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "comment-replies", map[string]string{
|
|
"number": "7", "comment-id": "abc",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error for non-integer --comment-id")
|
|
}
|
|
}
|