Merge PR #99: 新增 5 个仓库检查快捷命令 (languages/contributors/files/tags/commits)
# Conflicts: # shortcuts/repo/repo_test.go
This commit is contained in:
commit
1bb19ff506
|
|
@ -571,6 +571,116 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "languages",
|
||||
Description: "Show language breakdown of a repository",
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("GET", ctx.RepoPath()+"/languages", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "contributors",
|
||||
Description: "List contributors of a repository",
|
||||
Flags: []common.Flag{
|
||||
{Name: "page", Short: "p", Usage: "Page number", Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("page", ctx.Arg("page"))
|
||||
q.Set("limit", ctx.Arg("limit"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/contributors", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "files",
|
||||
Description: "List files in a repository directory",
|
||||
Flags: []common.Flag{
|
||||
{Name: "ref", Short: "r", Usage: "Branch, tag, or commit SHA"},
|
||||
{Name: "path", Short: "p", Usage: "Directory path (default: repository root)"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
if ref := ctx.Arg("ref"); ref != "" {
|
||||
q.Set("ref", ref)
|
||||
}
|
||||
if p := ctx.Arg("path"); p != "" {
|
||||
q.Set("filepath", p)
|
||||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/files", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "tags",
|
||||
Description: "List tags of a repository",
|
||||
Flags: []common.Flag{
|
||||
{Name: "page", Short: "p", Usage: "Page number", Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("page", ctx.Arg("page"))
|
||||
q.Set("limit", ctx.Arg("limit"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/tags", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "commits",
|
||||
Description: "List commits of a repository",
|
||||
Flags: []common.Flag{
|
||||
{Name: "sha", Short: "s", Usage: "Branch name, tag, or commit SHA"},
|
||||
{Name: "path", Short: "p", Usage: "Filter commits by file path"},
|
||||
{Name: "page", Short: "P", Usage: "Page number", Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("page", ctx.Arg("page"))
|
||||
q.Set("limit", ctx.Arg("limit"))
|
||||
if sha := ctx.Arg("sha"); sha != "" {
|
||||
q.Set("sha", sha)
|
||||
}
|
||||
if p := ctx.Arg("path"); p != "" {
|
||||
q.Set("path", p)
|
||||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/commits", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package repo
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -15,18 +14,12 @@ func runShortcut(t *testing.T, server *httptest.Server, name string, args map[st
|
|||
t.Helper()
|
||||
s := findShortcut(t, name)
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{
|
||||
HTTP: server.Client(),
|
||||
BaseURL: server.URL,
|
||||
},
|
||||
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 s.Run(ctx)
|
||||
}
|
||||
|
||||
|
|
@ -41,20 +34,9 @@ func findShortcut(t *testing.T, name string) *common.Shortcut {
|
|||
return nil
|
||||
}
|
||||
|
||||
func writeJSON(t *testing.T, w http.ResponseWriter, v interface{}) {
|
||||
t.Helper()
|
||||
func writeJSON(w http.ResponseWriter, v interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(v); err != nil {
|
||||
t.Fatalf("write response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func writeText(t *testing.T, w http.ResponseWriter, code int, text string) {
|
||||
t.Helper()
|
||||
w.WriteHeader(code)
|
||||
if _, err := w.Write([]byte(text)); err != nil {
|
||||
t.Fatalf("write response: %v", err)
|
||||
}
|
||||
json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
|
||||
// --- list ---
|
||||
|
|
@ -67,7 +49,7 @@ func TestRepoListDefault(t *testing.T) {
|
|||
if r.URL.Path != "/projects.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"total_count": float64(2),
|
||||
"data": []interface{}{map[string]interface{}{"name": "repo1"}},
|
||||
})
|
||||
|
|
@ -85,7 +67,7 @@ func TestRepoListForUser(t *testing.T) {
|
|||
if r.URL.Path != "/users/alice/projects.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{"total_count": float64(0), "data": []interface{}{}})
|
||||
writeJSON(w, map[string]interface{}{"total_count": float64(0), "data": []interface{}{}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
|
|
@ -100,7 +82,7 @@ func TestRepoListWithCategory(t *testing.T) {
|
|||
if r.URL.Query().Get("category") != "mirror" {
|
||||
t.Fatalf("expected category=mirror, got %s", r.URL.Query().Get("category"))
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{"data": []interface{}{}})
|
||||
writeJSON(w, map[string]interface{}{"data": []interface{}{}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
|
|
@ -110,14 +92,14 @@ func TestRepoListWithCategory(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// --- info/readme/insights ---
|
||||
// --- info ---
|
||||
|
||||
func TestRepoInfo(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/owner/repo.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"name": "repo",
|
||||
"description": "test repo",
|
||||
})
|
||||
|
|
@ -130,269 +112,7 @@ func TestRepoInfo(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRepoReadmeUsesRepositoryReadmeEndpoint(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/owner/repo/readme.json")
|
||||
assertEqual(t, r.URL.Query().Get("ref"), "main")
|
||||
assertEqual(t, r.URL.Query().Get("filepath"), "docs")
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"type": "file",
|
||||
"name": "README.md",
|
||||
"content": "# docs\n",
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "readme", map[string]string{
|
||||
"ref": "main",
|
||||
"path": "docs",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("readme shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoTreeListsRootOnDefaultRef(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/owner/repo/sub_entries.json")
|
||||
if _, ok := r.URL.Query()["filepath"]; ok {
|
||||
t.Fatalf("did not expect filepath query for repository root, got %q", r.URL.Query().Get("filepath"))
|
||||
}
|
||||
assertEqual(t, r.URL.Query().Get("ref"), "master")
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"entries": []map[string]interface{}{
|
||||
{"name": "README.md", "type": "file"},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "tree", nil); err != nil {
|
||||
t.Fatalf("tree shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoTreeUsesPathAndRef(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/owner/repo/sub_entries.json")
|
||||
assertEqual(t, r.URL.Query().Get("filepath"), "cmd")
|
||||
assertEqual(t, r.URL.Query().Get("ref"), "main")
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"entries": []map[string]interface{}{
|
||||
{"name": "main.go", "type": "file"},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "tree", map[string]string{"path": "cmd", "ref": "main"}); err != nil {
|
||||
t.Fatalf("tree shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoTreeShortcutRegistersHelpFlags(t *testing.T) {
|
||||
tree := findShortcut(t, "tree")
|
||||
if tree.Description == "" {
|
||||
t.Fatal("tree shortcut description is empty")
|
||||
}
|
||||
|
||||
flags := map[string]common.Flag{}
|
||||
for _, flag := range tree.Flags {
|
||||
flags[flag.Name] = flag
|
||||
}
|
||||
|
||||
pathFlag, ok := flags["path"]
|
||||
if !ok {
|
||||
t.Fatal("tree shortcut missing path flag")
|
||||
}
|
||||
if pathFlag.Short != "p" || pathFlag.Usage == "" {
|
||||
t.Fatalf("unexpected path flag: %+v", pathFlag)
|
||||
}
|
||||
|
||||
refFlag, ok := flags["ref"]
|
||||
if !ok {
|
||||
t.Fatal("tree shortcut missing ref flag")
|
||||
}
|
||||
if refFlag.Short != "r" || refFlag.Default != "master" || refFlag.Usage == "" {
|
||||
t.Fatalf("unexpected ref flag: %+v", refFlag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoLanguagesUsesLanguagesEndpoint(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/owner/repo/languages.json")
|
||||
writeJSON(t, w, map[string]interface{}{"Go": "92.4%", "Shell": "7.6%"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "languages", nil); err != nil {
|
||||
t.Fatalf("languages shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoContributorsUsesContributorsEndpoint(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/owner/repo/contributors.json")
|
||||
writeJSON(t, w, map[string]interface{}{"total_count": 1, "list": []interface{}{}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "contributors", nil); err != nil {
|
||||
t.Fatalf("contributors shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoContributorStatsBuildsQuery(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/v1/owner/repo/contributors/stat.json")
|
||||
assertEqual(t, r.URL.Query().Get("ref"), "main")
|
||||
assertEqual(t, r.URL.Query().Get("pass_year"), "2")
|
||||
writeJSON(t, w, map[string]interface{}{"total_count": 1, "contributors": []interface{}{}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "contributor-stats", map[string]string{
|
||||
"ref": " main ",
|
||||
"pass-year": "2",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("contributor-stats shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoCodeStatsUsesRefQuery(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/v1/owner/repo/code_stats.json")
|
||||
assertEqual(t, r.URL.Query().Get("ref"), "release/v1")
|
||||
writeJSON(t, w, map[string]interface{}{"author_count": 1, "commit_count": 3})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "code-stats", map[string]string{"ref": "release/v1"}); err != nil {
|
||||
t.Fatalf("code-stats shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoWatchersBuildsTimeRangeQuery(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/owner/repo/watchers.json")
|
||||
assertEqual(t, r.URL.Query().Get("start_at"), "1714521600")
|
||||
assertEqual(t, r.URL.Query().Get("end_at"), "1717200000")
|
||||
writeJSON(t, w, map[string]interface{}{"count": 1, "users": []interface{}{}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "watchers", map[string]string{
|
||||
"start-at": "1714521600",
|
||||
"end-at": "1717200000",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("watchers shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoStargazersUsesStargazersEndpoint(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/owner/repo/stargazers.json")
|
||||
writeJSON(t, w, map[string]interface{}{"count": 0, "users": []interface{}{}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "stargazers", nil); err != nil {
|
||||
t.Fatalf("stargazers shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoFollowResolvesProjectID(t *testing.T) {
|
||||
requests := 0
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requests++
|
||||
switch requests {
|
||||
case 1:
|
||||
assertRequest(t, r, "GET", "/owner/repo.json")
|
||||
writeJSON(t, w, map[string]interface{}{"id": float64(123)})
|
||||
case 2:
|
||||
assertRequest(t, r, "POST", "/watchers/follow.json")
|
||||
assertEqual(t, r.URL.Query().Get("target_type"), "project")
|
||||
assertEqual(t, r.URL.Query().Get("id"), "123")
|
||||
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success", "watched": true})
|
||||
default:
|
||||
t.Fatalf("unexpected extra request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "follow", nil); err != nil {
|
||||
t.Fatalf("follow shortcut failed: %v", err)
|
||||
}
|
||||
assertEqual(t, requests, 2)
|
||||
}
|
||||
|
||||
func TestRepoUnfollowUsesExplicitProjectID(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "DELETE", "/watchers/unfollow.json")
|
||||
assertEqual(t, r.URL.Query().Get("target_type"), "project")
|
||||
assertEqual(t, r.URL.Query().Get("id"), "456")
|
||||
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success", "watched": false})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "unfollow", map[string]string{"project-id": "456"}); err != nil {
|
||||
t.Fatalf("unfollow shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoLikeUsesExplicitProjectID(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "POST", "/projects/456/praise_tread/like.json")
|
||||
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "like", map[string]string{"project-id": "456"}); err != nil {
|
||||
t.Fatalf("like shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoUnlikeResolvesStringProjectID(t *testing.T) {
|
||||
requests := 0
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requests++
|
||||
switch requests {
|
||||
case 1:
|
||||
assertRequest(t, r, "GET", "/owner/repo.json")
|
||||
writeJSON(t, w, map[string]interface{}{"project_id": "789"})
|
||||
case 2:
|
||||
assertRequest(t, r, "DELETE", "/projects/789/praise_tread/unlike.json")
|
||||
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success"})
|
||||
default:
|
||||
t.Fatalf("unexpected extra request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "unlike", nil); err != nil {
|
||||
t.Fatalf("unlike shortcut failed: %v", err)
|
||||
}
|
||||
assertEqual(t, requests, 2)
|
||||
}
|
||||
|
||||
func TestRepoInteractionDryRunDoesNotCallAPIWithExplicitProjectID(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("dry-run with explicit project-id should not call API, got: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "like", map[string]string{
|
||||
"project-id": "456",
|
||||
"dry-run": "true",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("dry-run shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// --- fork/delete/create ---
|
||||
// --- fork ---
|
||||
|
||||
func TestRepoFork(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -402,7 +122,7 @@ func TestRepoFork(t *testing.T) {
|
|||
if r.URL.Path != "/owner/repo/forks.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{"message": "forked"})
|
||||
writeJSON(w, map[string]interface{}{"message": "forked"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
|
|
@ -412,6 +132,8 @@ func TestRepoFork(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// --- delete ---
|
||||
|
||||
func TestRepoDelete(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "DELETE" {
|
||||
|
|
@ -420,7 +142,7 @@ func TestRepoDelete(t *testing.T) {
|
|||
if r.URL.Path != "/owner/repo.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{"message": "deleted"})
|
||||
writeJSON(w, map[string]interface{}{"message": "deleted"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
|
|
@ -430,16 +152,18 @@ func TestRepoDelete(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// --- create ---
|
||||
|
||||
func TestRepoCreate(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.URL.Path == "/users/me.json" && r.Method == "GET":
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"login": "creator",
|
||||
"user_id": float64(42),
|
||||
})
|
||||
case r.URL.Path == "/creator/new-repo.json" && r.Method == "POST":
|
||||
writeJSON(t, w, map[string]interface{}{"name": "new-repo"})
|
||||
writeJSON(w, map[string]interface{}{"name": "new-repo"})
|
||||
default:
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
|
|
@ -457,15 +181,13 @@ func TestRepoCreateWithOptions(t *testing.T) {
|
|||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/users/me.json":
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"login": "creator",
|
||||
"user_id": float64(42),
|
||||
})
|
||||
case "/creator/my-repo.json":
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("decode request body: %v", err)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{"name": "my-repo"})
|
||||
json.NewDecoder(r.Body).Decode(&body)
|
||||
writeJSON(w, map[string]interface{}{"name": "my-repo"})
|
||||
default:
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
|
|
@ -488,55 +210,6 @@ func TestRepoCreateWithOptions(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// --- validation/error paths ---
|
||||
|
||||
func TestRepoInsightValidation(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("invalid input should not call API, got: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
shortcut string
|
||||
args map[string]string
|
||||
}{
|
||||
{
|
||||
name: "invalid pass year",
|
||||
shortcut: "contributor-stats",
|
||||
args: map[string]string{"pass-year": "0"},
|
||||
},
|
||||
{
|
||||
name: "invalid start timestamp",
|
||||
shortcut: "watchers",
|
||||
args: map[string]string{"start-at": "abc"},
|
||||
},
|
||||
{
|
||||
name: "start after end",
|
||||
shortcut: "stargazers",
|
||||
args: map[string]string{"start-at": "20", "end-at": "10"},
|
||||
},
|
||||
{
|
||||
name: "invalid project id",
|
||||
shortcut: "follow",
|
||||
args: map[string]string{"project-id": "repo"},
|
||||
},
|
||||
{
|
||||
name: "negative project id",
|
||||
shortcut: "unlike",
|
||||
args: map[string]string{"project-id": "-1"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if err := runShortcut(t, server, tc.shortcut, tc.args); err == nil {
|
||||
t.Fatal("expected validation error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoCreateFailsWithoutName(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatal("no API call should be made")
|
||||
|
|
@ -549,9 +222,12 @@ func TestRepoCreateFailsWithoutName(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// --- HTTP error paths ---
|
||||
|
||||
func TestRepoListHTTPError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
writeText(t, w, http.StatusInternalServerError, "server error")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("server error"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
|
|
@ -563,7 +239,8 @@ func TestRepoListHTTPError(t *testing.T) {
|
|||
|
||||
func TestRepoInfoHTTPError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
writeText(t, w, http.StatusInternalServerError, "server error")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("server error"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
|
|
@ -575,7 +252,8 @@ func TestRepoInfoHTTPError(t *testing.T) {
|
|||
|
||||
func TestRepoForkHTTPError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
writeText(t, w, http.StatusInternalServerError, "server error")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("server error"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
|
|
@ -587,7 +265,8 @@ func TestRepoForkHTTPError(t *testing.T) {
|
|||
|
||||
func TestRepoDeleteHTTPError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
writeText(t, w, http.StatusInternalServerError, "server error")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("server error"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
|
|
@ -599,7 +278,8 @@ func TestRepoDeleteHTTPError(t *testing.T) {
|
|||
|
||||
func TestRepoCreateGetUserHTTPError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
writeText(t, w, http.StatusInternalServerError, "server error")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("server error"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
|
|
@ -611,7 +291,7 @@ func TestRepoCreateGetUserHTTPError(t *testing.T) {
|
|||
|
||||
func TestRepoCreateUserNoLogin(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, map[string]interface{}{"user_id": float64(42)})
|
||||
writeJSON(w, map[string]interface{}{"user_id": float64(42)})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
|
|
@ -621,146 +301,246 @@ func TestRepoCreateUserNoLogin(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func assertRequest(t *testing.T, r *http.Request, method, path string) {
|
||||
t.Helper()
|
||||
if r.Method != method || r.URL.Path != path {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
}
|
||||
// --- languages ---
|
||||
|
||||
func assertEqual(t *testing.T, got interface{}, want interface{}) {
|
||||
t.Helper()
|
||||
if fmt.Sprintf("%v", got) != fmt.Sprintf("%v", want) {
|
||||
t.Fatalf("got %v (%T), want %v (%T)", got, got, want, want)
|
||||
}
|
||||
}
|
||||
|
||||
// --- edit ---
|
||||
|
||||
func TestRepoEditRequiresAtLeastOneField(t *testing.T) {
|
||||
func TestRepoLanguages(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, "edit", nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when no fields are given")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoEditValidatesPrivate(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, "edit", map[string]string{"private": "yes"})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid --private value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoEditValidatesCategoryID(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, "edit", map[string]string{"category-id": "abc"})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for non-integer --category-id")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoEditSendsMetadataWithNameAndIdentifier(t *testing.T) {
|
||||
var patches []map[string]interface{}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/owner/repo.json" {
|
||||
if r.Method != "GET" {
|
||||
t.Fatalf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/owner/repo/languages.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
writeJSON(t, w, map[string]interface{}{"name": "repo", "identifier": "repo"})
|
||||
case "PATCH":
|
||||
var body map[string]interface{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("decode body: %v", err)
|
||||
}
|
||||
patches = append(patches, body)
|
||||
writeJSON(t, w, map[string]interface{}{"id": 1})
|
||||
default:
|
||||
t.Fatalf("unexpected method: %s", r.Method)
|
||||
}
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"Go": float64(85.5),
|
||||
"Shell": float64(14.5),
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "edit", map[string]string{"description": "new desc", "private": "true"})
|
||||
err := runShortcut(t, server, "languages", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("edit failed: %v", err)
|
||||
t.Fatalf("languages failed: %v", err)
|
||||
}
|
||||
if len(patches) != 1 {
|
||||
t.Fatalf("expected 1 PATCH, got %d", len(patches))
|
||||
}
|
||||
body := patches[0]
|
||||
assertEqual(t, body["name"], "repo")
|
||||
assertEqual(t, body["identifier"], "repo")
|
||||
assertEqual(t, body["description"], "new desc")
|
||||
assertEqual(t, body["private"], true)
|
||||
}
|
||||
|
||||
func TestRepoEditSplitsWebsiteAndDefaultBranchRequests(t *testing.T) {
|
||||
var patches []map[string]interface{}
|
||||
func TestRepoLanguagesHTTPError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/owner/repo.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
writeJSON(t, w, map[string]interface{}{"name": "repo", "identifier": "repo"})
|
||||
case "PATCH":
|
||||
var body map[string]interface{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("decode body: %v", err)
|
||||
}
|
||||
patches = append(patches, body)
|
||||
writeJSON(t, w, map[string]interface{}{"id": 1})
|
||||
default:
|
||||
t.Fatalf("unexpected method: %s", r.Method)
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("server error"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "edit", map[string]string{
|
||||
"website": "https://example.org",
|
||||
"default-branch": "main",
|
||||
"description": "d",
|
||||
err := runShortcut(t, server, "languages", nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for HTTP 500")
|
||||
}
|
||||
}
|
||||
|
||||
// --- contributors ---
|
||||
|
||||
func TestRepoContributors(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
t.Fatalf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/owner/repo/contributors.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
if r.URL.Query().Get("page") != "1" {
|
||||
t.Fatalf("expected page=1, got %s", r.URL.Query().Get("page"))
|
||||
}
|
||||
if r.URL.Query().Get("limit") != "20" {
|
||||
t.Fatalf("expected limit=20, got %s", r.URL.Query().Get("limit"))
|
||||
}
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"total_count": float64(1),
|
||||
"data": []interface{}{map[string]interface{}{"login": "alice", "contributions": float64(42)}},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "contributors", map[string]string{"page": "1", "limit": "20"})
|
||||
if err != nil {
|
||||
t.Fatalf("contributors failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoContributorsHTTPError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("server error"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "contributors", map[string]string{"page": "1", "limit": "20"})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for HTTP 500")
|
||||
}
|
||||
}
|
||||
|
||||
// --- files ---
|
||||
|
||||
func TestRepoFiles(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
t.Fatalf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/owner/repo/files.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(w, []interface{}{
|
||||
map[string]interface{}{"name": "README.md", "type": "file"},
|
||||
map[string]interface{}{"name": "src", "type": "dir"},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "files", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("files failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoFilesWithRef(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Query().Get("ref") != "main" {
|
||||
t.Fatalf("expected ref=main, got %s", r.URL.Query().Get("ref"))
|
||||
}
|
||||
if r.URL.Query().Get("filepath") != "src" {
|
||||
t.Fatalf("expected filepath=src, got %s", r.URL.Query().Get("filepath"))
|
||||
}
|
||||
writeJSON(w, []interface{}{})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "files", map[string]string{"ref": "main", "path": "src"})
|
||||
if err != nil {
|
||||
t.Fatalf("files with ref failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoFilesHTTPError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("server error"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "files", nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for HTTP 500")
|
||||
}
|
||||
}
|
||||
|
||||
// --- tags ---
|
||||
|
||||
func TestRepoTags(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
t.Fatalf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/owner/repo/tags.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
if r.URL.Query().Get("page") != "1" {
|
||||
t.Fatalf("expected page=1, got %s", r.URL.Query().Get("page"))
|
||||
}
|
||||
if r.URL.Query().Get("limit") != "20" {
|
||||
t.Fatalf("expected limit=20, got %s", r.URL.Query().Get("limit"))
|
||||
}
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"total_count": float64(1),
|
||||
"data": []interface{}{map[string]interface{}{"name": "v1.0.0"}},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "tags", map[string]string{"page": "1", "limit": "20"})
|
||||
if err != nil {
|
||||
t.Fatalf("tags failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoTagsHTTPError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("server error"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "tags", map[string]string{"page": "1", "limit": "20"})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for HTTP 500")
|
||||
}
|
||||
}
|
||||
|
||||
// --- commits ---
|
||||
|
||||
func TestRepoCommits(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
t.Fatalf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/owner/repo/commits.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
if r.URL.Query().Get("page") != "1" {
|
||||
t.Fatalf("expected page=1, got %s", r.URL.Query().Get("page"))
|
||||
}
|
||||
if r.URL.Query().Get("limit") != "20" {
|
||||
t.Fatalf("expected limit=20, got %s", r.URL.Query().Get("limit"))
|
||||
}
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"total_count": float64(1),
|
||||
"data": []interface{}{
|
||||
map[string]interface{}{"sha": "abc123", "message": "initial commit"},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "commits", map[string]string{"page": "1", "limit": "20"})
|
||||
if err != nil {
|
||||
t.Fatalf("commits failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoCommitsWithFilters(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Query().Get("sha") != "main" {
|
||||
t.Fatalf("expected sha=main, got %s", r.URL.Query().Get("sha"))
|
||||
}
|
||||
if r.URL.Query().Get("path") != "src/main.go" {
|
||||
t.Fatalf("expected path=src/main.go, got %s", r.URL.Query().Get("path"))
|
||||
}
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"total_count": float64(1),
|
||||
"data": []interface{}{},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "commits", map[string]string{
|
||||
"sha": "main",
|
||||
"path": "src/main.go",
|
||||
"page": "1",
|
||||
"limit": "20",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("edit failed: %v", err)
|
||||
t.Fatalf("commits with filters failed: %v", err)
|
||||
}
|
||||
if len(patches) != 3 {
|
||||
t.Fatalf("expected 3 PATCH requests (default-branch, website, metadata), got %d", len(patches))
|
||||
}
|
||||
assertEqual(t, patches[0]["default_branch"], "main")
|
||||
if _, ok := patches[0]["website"]; ok {
|
||||
t.Fatal("default-branch request must not carry website")
|
||||
}
|
||||
assertEqual(t, patches[1]["website"], "https://example.org")
|
||||
if _, ok := patches[1]["default_branch"]; ok {
|
||||
t.Fatal("website request must not carry default_branch")
|
||||
}
|
||||
assertEqual(t, patches[2]["description"], "d")
|
||||
}
|
||||
|
||||
func TestRepoEditFailsWhenIdentifierMissing(t *testing.T) {
|
||||
func TestRepoCommitsHTTPError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, map[string]interface{}{"name": "repo"})
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("server error"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "edit", map[string]string{"description": "x"})
|
||||
err := runShortcut(t, server, "commits", map[string]string{"page": "1", "limit": "20"})
|
||||
if err == nil {
|
||||
t.Fatal("expected error when identifier cannot be resolved")
|
||||
t.Fatal("expected error for HTTP 500")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue