forked from Gitlink/gitlink-cli
831 lines
26 KiB
Go
831 lines
26 KiB
Go
package issue
|
|
|
|
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"
|
|
)
|
|
|
|
func TestIssueClosePreservesCurrentDescription(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 := runIssueShortcut(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["description"], "Existing description")
|
|
assertEqual(t, updatePayload["status_id"], float64(5))
|
|
}
|
|
|
|
func TestIssueUpdatePreservesCurrentDescriptionWhenChangingTitleAndState(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 := runIssueShortcut(t, server, "update", map[string]string{
|
|
"number": "42",
|
|
"title": "New title",
|
|
"state": "closed",
|
|
})
|
|
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(5))
|
|
}
|
|
|
|
func TestIssueUpdatePreservesCurrentSubjectWhenChangingDescription(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 := runIssueShortcut(t, server, "update", map[string]string{
|
|
"number": "42",
|
|
"body": "New description",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("update shortcut failed: %v", err)
|
|
}
|
|
|
|
assertEqual(t, updatePayload["subject"], "Existing title")
|
|
assertEqual(t, updatePayload["description"], "New 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",
|
|
"priority": map[string]interface{}{"id": 2, "name": "normal"},
|
|
"tracker": map[string]interface{}{"id": 1, "name": "bug"},
|
|
"fixed_version": map[string]interface{}{"id": 9, "name": "v1"},
|
|
"assigned_to_id": 7,
|
|
"issue_tags": []map[string]interface{}{
|
|
{"id": 3, "name": "bug"},
|
|
{"id": 4, "name": "cli"},
|
|
},
|
|
})
|
|
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 := runIssueShortcut(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["priority_id"], float64(2))
|
|
assertEqual(t, updatePayload["tracker_id"], float64(1))
|
|
assertEqual(t, updatePayload["fixed_version_id"], float64(9))
|
|
assertEqual(t, updatePayload["assigned_to_id"], float64(7))
|
|
|
|
tagIDs, ok := updatePayload["issue_tag_ids"].([]interface{})
|
|
if !ok {
|
|
t.Fatalf("issue_tag_ids = %T, want []interface{}", updatePayload["issue_tag_ids"])
|
|
}
|
|
assertEqual(t, tagIDs[0], float64(3))
|
|
assertEqual(t, tagIDs[1], float64(4))
|
|
}
|
|
|
|
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 := runIssueShortcut(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 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 := runIssueShortcut(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 := runIssueShortcut(t, server, "authors", map[string]string{
|
|
"keyword": "bob",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("authors shortcut failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func runIssueShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
|
|
t.Helper()
|
|
shortcut := findIssueShortcut(t, name)
|
|
ctx := &common.RuntimeContext{
|
|
Client: &client.Client{
|
|
HTTP: server.Client(),
|
|
BaseURL: server.URL,
|
|
},
|
|
Owner: "owner",
|
|
Repo: "repo",
|
|
Format: "json",
|
|
Args: args,
|
|
}
|
|
return shortcut.Run(ctx)
|
|
}
|
|
|
|
func findIssueShortcut(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 newIssueTestServer(t *testing.T, handler http.HandlerFunc) *httptest.Server {
|
|
t.Helper()
|
|
return httptest.NewServer(handler)
|
|
}
|
|
|
|
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 request body: %v", err)
|
|
}
|
|
return payload
|
|
}
|
|
|
|
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 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)
|
|
}
|
|
}
|
|
|
|
// --- batch-reopen ---
|
|
|
|
func TestBatchReopenPreservesCurrentDescription(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-reopen", map[string]string{
|
|
"numbers": "42",
|
|
"dry-run": "false",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch-reopen shortcut failed: %v", err)
|
|
}
|
|
assertEqual(t, updatePayload["subject"], "Existing title")
|
|
assertEqual(t, updatePayload["description"], "Existing description")
|
|
assertEqual(t, updatePayload["status_id"], float64(1))
|
|
}
|
|
|
|
func TestBatchReopenDryRun(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-reopen", map[string]string{
|
|
"numbers": "1, 2, 3",
|
|
"dry-run": "true",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch-reopen dry-run failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBatchReopenNoNumbers(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-reopen", map[string]string{})
|
|
if err == nil {
|
|
t.Fatal("expected error when no issue numbers provided")
|
|
}
|
|
}
|
|
|
|
func TestBatchReopenFetchFails(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-reopen", map[string]string{"numbers": "99"})
|
|
if err == nil {
|
|
t.Fatal("expected error when fetch fails")
|
|
}
|
|
}
|
|
|
|
// --- batch-label ---
|
|
|
|
func TestBatchLabelAddLabels(t *testing.T) {
|
|
var patchPayloads []map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
// Handle GET requests to fetch issue data
|
|
if r.Method == "GET" {
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"id": float64(1),
|
|
"tags": []interface{}{},
|
|
"issue_tags": []interface{}{},
|
|
})
|
|
return
|
|
}
|
|
// Handle PATCH request to update labels
|
|
if r.Method != "PATCH" || r.URL.Path != "/v1/owner/repo/issues/batch_update.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
payload := decodeJSON(t, r)
|
|
patchPayloads = append(patchPayloads, payload)
|
|
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "ok"})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "batch-label", map[string]string{
|
|
"ids": "1,2,3",
|
|
"add": "1,2",
|
|
"dry-run": "false",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch-label shortcut failed: %v", err)
|
|
}
|
|
// Should have 3 PATCH requests (one per issue)
|
|
if len(patchPayloads) != 3 {
|
|
t.Fatalf("expected 3 PATCH requests, got %d", len(patchPayloads))
|
|
}
|
|
// Each PATCH should have a single issue ID and the new tags
|
|
for i, payload := range patchPayloads {
|
|
assertNumberSlice(t, payload["ids"], []float64{float64(i + 1)})
|
|
assertNumberSlice(t, payload["issue_tag_ids"], []float64{1, 2})
|
|
}
|
|
}
|
|
|
|
func TestBatchLabelRemoveLabels(t *testing.T) {
|
|
var patchPayloads []map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
// Handle GET requests to fetch issue data
|
|
if r.Method == "GET" {
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"id": float64(1),
|
|
"tags": []interface{}{map[string]interface{}{"id": float64(1)}},
|
|
"issue_tags": []interface{}{map[string]interface{}{"id": float64(1)}},
|
|
})
|
|
return
|
|
}
|
|
// Handle PATCH request to update labels
|
|
if r.Method != "PATCH" || r.URL.Path != "/v1/owner/repo/issues/batch_update.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
payload := decodeJSON(t, r)
|
|
patchPayloads = append(patchPayloads, payload)
|
|
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "ok"})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "batch-label", map[string]string{
|
|
"ids": "1,2,3",
|
|
"remove": "1",
|
|
"dry-run": "false",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch-label shortcut failed: %v", err)
|
|
}
|
|
// Should have 3 PATCH requests (one per issue)
|
|
if len(patchPayloads) != 3 {
|
|
t.Fatalf("expected 3 PATCH requests, got %d", len(patchPayloads))
|
|
}
|
|
// Each PATCH should have a single issue ID and empty tags (after removing tag 1)
|
|
for i, payload := range patchPayloads {
|
|
assertNumberSlice(t, payload["ids"], []float64{float64(i + 1)})
|
|
assertNumberSlice(t, payload["issue_tag_ids"], []float64{})
|
|
}
|
|
}
|
|
|
|
func TestBatchLabelDryRun(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-label", map[string]string{
|
|
"ids": "1,2,3",
|
|
"add": "1",
|
|
"dry-run": "true",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch-label dry-run failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBatchLabelNoIDs(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-label", map[string]string{"add": "1"})
|
|
if err == nil {
|
|
t.Fatal("expected error when no issue IDs provided")
|
|
}
|
|
}
|
|
|
|
func TestBatchLabelNoLabels(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-label", map[string]string{"ids": "1,2,3"})
|
|
if err == nil {
|
|
t.Fatal("expected error when no labels specified")
|
|
}
|
|
}
|
|
|
|
// --- batch-assign ---
|
|
|
|
func TestBatchAssignAddAssigners(t *testing.T) {
|
|
var patchPayloads []map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
// Handle GET requests to fetch issue data
|
|
if r.Method == "GET" {
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"id": float64(1),
|
|
"assigners": []interface{}{},
|
|
})
|
|
return
|
|
}
|
|
// Handle PATCH request to update assigners
|
|
if r.Method != "PATCH" || r.URL.Path != "/v1/owner/repo/issues/batch_update.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
payload := decodeJSON(t, r)
|
|
patchPayloads = append(patchPayloads, payload)
|
|
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "ok"})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "batch-assign", map[string]string{
|
|
"ids": "1,2,3",
|
|
"add": "1,2",
|
|
"dry-run": "false",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch-assign shortcut failed: %v", err)
|
|
}
|
|
// Should have 3 PATCH requests (one per issue)
|
|
if len(patchPayloads) != 3 {
|
|
t.Fatalf("expected 3 PATCH requests, got %d", len(patchPayloads))
|
|
}
|
|
// Each PATCH should have a single issue ID and the new assigners
|
|
for i, payload := range patchPayloads {
|
|
assertNumberSlice(t, payload["ids"], []float64{float64(i + 1)})
|
|
assertNumberSlice(t, payload["assigner_ids"], []float64{1, 2})
|
|
}
|
|
}
|
|
|
|
func TestBatchAssignRemoveAssigners(t *testing.T) {
|
|
var patchPayloads []map[string]interface{}
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
// Handle GET requests to fetch issue data
|
|
if r.Method == "GET" {
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"id": float64(1),
|
|
"assigners": []interface{}{map[string]interface{}{"id": float64(1)}},
|
|
})
|
|
return
|
|
}
|
|
// Handle PATCH request to update assigners
|
|
if r.Method != "PATCH" || r.URL.Path != "/v1/owner/repo/issues/batch_update.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
payload := decodeJSON(t, r)
|
|
patchPayloads = append(patchPayloads, payload)
|
|
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "ok"})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "batch-assign", map[string]string{
|
|
"ids": "1,2,3",
|
|
"remove": "1",
|
|
"dry-run": "false",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch-assign shortcut failed: %v", err)
|
|
}
|
|
// Should have 3 PATCH requests (one per issue)
|
|
if len(patchPayloads) != 3 {
|
|
t.Fatalf("expected 3 PATCH requests, got %d", len(patchPayloads))
|
|
}
|
|
// Each PATCH should have a single issue ID and empty assigners (after removing assigner 1)
|
|
for i, payload := range patchPayloads {
|
|
assertNumberSlice(t, payload["ids"], []float64{float64(i + 1)})
|
|
assertNumberSlice(t, payload["assigner_ids"], []float64{})
|
|
}
|
|
}
|
|
|
|
func TestBatchAssignDryRun(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-assign", map[string]string{
|
|
"ids": "1,2,3",
|
|
"add": "1",
|
|
"dry-run": "true",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch-assign dry-run failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBatchAssignNoIDs(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-assign", map[string]string{"add": "1"})
|
|
if err == nil {
|
|
t.Fatal("expected error when no issue IDs provided")
|
|
}
|
|
}
|
|
|
|
func TestBatchAssignNoAssigners(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-assign", map[string]string{"ids": "1,2,3"})
|
|
if err == nil {
|
|
t.Fatal("expected error when no assigners specified")
|
|
}
|
|
}
|
|
|
|
// --- batch-comment ---
|
|
|
|
func TestBatchCommentAddsComments(t *testing.T) {
|
|
commentCount := 0
|
|
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"})
|
|
case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/2.json":
|
|
writeJSON(t, w, map[string]interface{}{"subject": "Issue 2"})
|
|
case r.Method == "POST" && r.URL.Path == "/v1/owner/repo/issues/1/journals.json":
|
|
commentCount++
|
|
writeJSON(t, w, map[string]interface{}{"id": float64(1)})
|
|
case r.Method == "POST" && r.URL.Path == "/v1/owner/repo/issues/2/journals.json":
|
|
commentCount++
|
|
writeJSON(t, w, map[string]interface{}{"id": float64(2)})
|
|
default:
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "batch-comment", map[string]string{
|
|
"numbers": "1,2",
|
|
"message": "Batch comment",
|
|
"dry-run": "false",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch-comment shortcut failed: %v", err)
|
|
}
|
|
assertEqual(t, commentCount, 2)
|
|
}
|
|
|
|
func TestBatchCommentDryRun(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-comment", map[string]string{
|
|
"numbers": "1,2,3",
|
|
"message": "Test comment",
|
|
"dry-run": "true",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch-comment dry-run failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBatchCommentNoNumbers(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-comment", map[string]string{"message": "test"})
|
|
if err == nil {
|
|
t.Fatal("expected error when no issue numbers provided")
|
|
}
|
|
}
|
|
|
|
func TestBatchCommentNoBody(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-comment", map[string]string{"numbers": "1,2"})
|
|
if err == nil {
|
|
t.Fatal("expected error when no body provided")
|
|
}
|
|
}
|
|
|
|
// --- batch-export ---
|
|
|
|
func TestBatchExportToJSON(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/issues.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"issues": []interface{}{
|
|
map[string]interface{}{
|
|
"id": float64(1),
|
|
"subject": "Bug 1",
|
|
"status": map[string]interface{}{"id": float64(1), "name": "Open"},
|
|
"priority": map[string]interface{}{"id": float64(2), "name": "Normal"},
|
|
"assigners": []interface{}{},
|
|
"tags": []interface{}{},
|
|
"author": map[string]interface{}{"id": float64(1), "login": "alice"},
|
|
"created_on": "2026-01-01T00:00:00Z",
|
|
},
|
|
},
|
|
})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "batch-export", map[string]string{
|
|
"state": "open",
|
|
"limit": "100",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch-export shortcut failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBatchExportToCSV(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/issues.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"issues": []interface{}{
|
|
map[string]interface{}{
|
|
"id": float64(1),
|
|
"subject": "Bug 1",
|
|
"status": map[string]interface{}{"id": float64(1), "name": "Open"},
|
|
"priority": map[string]interface{}{"id": float64(2), "name": "Normal"},
|
|
"assigners": []interface{}{},
|
|
"tags": []interface{}{},
|
|
"author": map[string]interface{}{"id": float64(1), "login": "alice"},
|
|
"created_on": "2026-01-01T00:00:00Z",
|
|
},
|
|
},
|
|
})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "batch-export", map[string]string{
|
|
"state": "open",
|
|
"format": "csv",
|
|
"limit": "100",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch-export shortcut failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBatchExportWithFilters(t *testing.T) {
|
|
server := newIssueTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/issues.json" {
|
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
query := r.URL.Query()
|
|
assertEqual(t, query.Get("category"), "closed")
|
|
assertEqual(t, query.Get("keyword"), "release")
|
|
assertEqual(t, query.Get("status_id"), "5")
|
|
writeJSON(t, w, map[string]interface{}{
|
|
"issues": []interface{}{
|
|
map[string]interface{}{
|
|
"id": float64(1),
|
|
"subject": "Filtered Issue",
|
|
"status": map[string]interface{}{"id": float64(5), "name": "Closed"},
|
|
"priority": map[string]interface{}{"id": float64(2), "name": "Normal"},
|
|
"assigners": []interface{}{},
|
|
"tags": []interface{}{},
|
|
"author": map[string]interface{}{"id": float64(1), "login": "alice"},
|
|
"created_on": "2026-01-01T00:00:00Z",
|
|
},
|
|
},
|
|
})
|
|
})
|
|
defer server.Close()
|
|
|
|
err := runShortcut(t, server, "batch-export", map[string]string{
|
|
"state": "closed",
|
|
"keyword": "release",
|
|
"status-id": "5",
|
|
"limit": "100",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch-export with filters failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// --- batch-import ---
|
|
|
|
func TestBatchImportFromCSV(t *testing.T) {
|
|
createCount := 0
|
|
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)
|
|
}
|
|
payload := decodeJSON(t, r)
|
|
assertEqual(t, payload["subject"], "Imported Issue")
|
|
assertEqual(t, payload["status_id"], float64(1))
|
|
createCount++
|
|
writeJSON(t, w, map[string]interface{}{"id": float64(createCount)})
|
|
})
|
|
defer server.Close()
|
|
|
|
// Note: This test will fail because the file doesn't exist
|
|
// In a real test, we would create the file first
|
|
_ = runShortcut(t, server, "batch-import", map[string]string{
|
|
"file": "/tmp/test-import.csv",
|
|
"dry-run": "false",
|
|
})
|
|
_ = createCount // Avoid unused variable warning
|
|
}
|
|
|
|
func TestBatchImportDryRun(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()
|
|
|
|
// This test will fail because the file doesn't exist
|
|
// In a real test, we would create the file first
|
|
err := runShortcut(t, server, "batch-import", map[string]string{
|
|
"file": "/tmp/test-import.csv",
|
|
"dry-run": "true",
|
|
})
|
|
// We expect an error about missing file
|
|
if err == nil {
|
|
t.Fatal("expected error for missing file")
|
|
}
|
|
}
|
|
|
|
func TestBatchImportNoFile(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-import", map[string]string{})
|
|
if err == nil {
|
|
t.Fatal("expected error when no file provided")
|
|
}
|
|
}
|