Files
GitOcean-Old/internal/app/cli_actions_test.go
T
2026-06-08 15:03:41 -05:00

187 lines
8.1 KiB
Go

package app
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
func TestCLIRepoMutatingHTTPCommands(t *testing.T) {
t.Setenv("GITOOCEAN_CONFIG", filepath.Join(t.TempDir(), "client.json"))
seen := map[string]int{}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key := r.Method + " " + r.URL.Path
seen[key]++
if r.Header.Get("Authorization") != "Bearer tok" {
t.Fatalf("missing auth for %s: %q", key, r.Header.Get("Authorization"))
}
switch key {
case "POST /api/repos":
writeJSON(w, http.StatusCreated, Repository{Owner: "alice", Name: "demo", Visibility: "public", Description: "desc", DefaultBranch: "main"})
case "PATCH /api/repos/alice/demo":
writeJSON(w, http.StatusOK, Repository{Owner: "alice", Name: "demo", Visibility: "private", Description: "new", DefaultBranch: "main", Archived: seen[key] > 1})
case "DELETE /api/repos/alice/demo":
if r.URL.Query().Get("force") != "true" {
t.Fatalf("expected force=true, got %s", r.URL.RawQuery)
}
writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
case "POST /api/repos/bob/demo/fork":
writeJSON(w, http.StatusCreated, Repository{Owner: "alice", Name: "fork", Visibility: "public", DefaultBranch: "main"})
case "GET /api/repos/alice/demo/collaborators":
writeJSON(w, http.StatusOK, []Collaborator{{Username: "bob", Role: "write"}})
case "POST /api/repos/alice/demo/collaborators":
writeJSON(w, http.StatusOK, map[string]string{"username": "bob", "role": "read"})
case "DELETE /api/repos/alice/demo/collaborators/bob":
writeJSON(w, http.StatusOK, map[string]string{"status": "removed"})
default:
t.Fatalf("unexpected request %s", key)
}
}))
defer ts.Close()
if err := saveConfig(Config{Server: ts.URL, Token: "tok", Username: "alice"}); err != nil {
t.Fatal(err)
}
checks := []struct {
name string
fn func() error
want string
}{
{"create", func() error { return cliRepoCreate([]string{"demo", "--public", "--description", "desc"}) }, "Created public repository"},
{"create json", func() error { return cliRepoCreate([]string{"demo", "--public", "--json"}) }, `"owner": "alice"`},
{"set", func() error {
return cliRepoSet([]string{"alice/demo", "--description", "new", "--visibility", "private"})
}, "Visibility: private"},
{"archive", func() error { return cliRepoArchive([]string{"alice/demo"}, true) }, "Archived: true"},
{"delete", func() error { return cliRepoDelete([]string{"alice/demo", "--force"}) }, "Deleted alice/demo"},
{"fork", func() error { return cliRepoFork([]string{"bob/demo", "--name", "fork"}) }, "Forked bob/demo to alice/fork"},
{"collaborators", func() error { return cliRepoCollaborators([]string{"alice/demo"}) }, "bob"},
{"collaborator add", func() error { return cliRepoCollaborator([]string{"add", "alice/demo", "bob", "--role", "read"}) }, "Added bob as read"},
{"collaborator remove", func() error { return cliRepoCollaborator([]string{"remove", "alice/demo", "bob"}) }, ""},
}
for _, tc := range checks {
t.Run(tc.name, func(t *testing.T) {
out := captureStdout(t, func() {
if err := tc.fn(); err != nil {
t.Fatal(err)
}
})
if !strings.Contains(out, tc.want) {
t.Fatalf("output missing %q: %s", tc.want, out)
}
})
}
}
func TestCLIPRMutatingHTTPCommands(t *testing.T) {
t.Setenv("GITOOCEAN_CONFIG", filepath.Join(t.TempDir(), "client.json"))
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key := r.Method + " " + r.URL.Path
switch key {
case "POST /api/repos/alice/demo/pulls":
writeJSON(w, http.StatusCreated, PullRequest{Number: 1, Status: "open", Title: "Fix", Author: "alice", SourceOwner: "alice", SourceRepo: "demo", SourceBranch: "feature", TargetOwner: "alice", TargetRepo: "demo", TargetBranch: "main"})
case "GET /api/repos/alice/demo/pulls/1/diff":
w.Header().Set("Content-Type", "text/plain")
_, _ = fmt.Fprint(w, "diff --git a/file b/file\n")
case "POST /api/repos/alice/demo/pulls/1/comments":
writeJSON(w, http.StatusCreated, PRComment{Author: "alice", Body: "hello"})
case "POST /api/repos/alice/demo/pulls/1/close", "POST /api/repos/alice/demo/pulls/1/merge":
writeJSON(w, http.StatusOK, PullRequest{Number: 1, Status: strings.TrimPrefix(r.URL.Path, "/api/repos/alice/demo/pulls/1/"), Title: "Fix", Author: "alice", SourceOwner: "alice", SourceRepo: "demo", SourceBranch: "feature", TargetOwner: "alice", TargetRepo: "demo", TargetBranch: "main"})
default:
t.Fatalf("unexpected request %s", key)
}
}))
defer ts.Close()
if err := saveConfig(Config{Server: ts.URL, Token: "tok", Username: "alice"}); err != nil {
t.Fatal(err)
}
checks := []struct {
name string
fn func() error
want string
}{
{"create same", func() error {
return cliPRCreate([]string{"--repo", "alice/demo", "--from", "feature", "--to", "main", "--title", "Fix"})
}, "#1 open: Fix"},
{"create cross", func() error {
return cliPRCreate([]string{"--from", "alice/demo:feature", "--to", "alice/demo:main", "--title", "Fix"})
}, "#1 open: Fix"},
{"diff", func() error { return cliPRDiff([]string{"alice/demo", "1"}) }, "diff --git"},
{"comment", func() error { return cliPRComment([]string{"alice/demo", "1", "hello"}) }, "Commented as alice"},
{"close", func() error { return cliPRAction([]string{"alice/demo", "1"}, "close") }, "#1 close: Fix"},
{"merge", func() error { return cliPRAction([]string{"alice/demo", "1"}, "merge") }, "#1 merge: Fix"},
}
for _, tc := range checks {
t.Run(tc.name, func(t *testing.T) {
out := captureStdout(t, func() {
if err := tc.fn(); err != nil {
t.Fatal(err)
}
})
if !strings.Contains(out, tc.want) {
t.Fatalf("output missing %q: %s", tc.want, out)
}
})
}
}
func TestCLITokenAdminAndBackupCommands(t *testing.T) {
t.Setenv("GITOOCEAN_CONFIG", filepath.Join(t.TempDir(), "client.json"))
backupCfgPath := filepath.Join(t.TempDir(), "server.json")
t.Setenv("GITOOCEAN_SERVER_CONFIG", backupCfgPath)
storage := t.TempDir()
if err := os.MkdirAll(filepath.Join(storage, "repos", "alice", "demo.git"), 0755); err != nil {
t.Fatal(err)
}
if err := saveServerConfig(backupCfgPath, ServerConfig{Storage: storage, MySQLDSN: "u:p@tcp(127.0.0.1:3306)/gitocean"}); err != nil {
t.Fatal(err)
}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method + " " + r.URL.Path {
case "GET /api/tokens":
writeJSON(w, http.StatusOK, []TokenInfo{{ID: 1, Active: true}})
case "DELETE /api/tokens/1", "POST /api/tokens/prune", "DELETE /api/admin/users/bob", "POST /api/admin/tokens/prune":
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
case "GET /api/admin/users":
writeJSON(w, http.StatusOK, []User{{Username: "alice", Email: "alice@example.com", IsAdmin: true}})
case "GET /api/admin/repos":
writeJSON(w, http.StatusOK, []Repository{{Owner: "alice", Name: "demo", Visibility: "public"}})
default:
t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
}
}))
defer ts.Close()
if err := saveConfig(Config{Server: ts.URL, Token: "tok", Username: "alice"}); err != nil {
t.Fatal(err)
}
checks := []struct {
name string
fn func() error
want string
}{
{"token list", func() error { return cliToken([]string{"list"}) }, "active"},
{"token revoke", func() error { return cliToken([]string{"revoke", "1"}) }, ""},
{"token prune", func() error { return cliToken([]string{"prune"}) }, ""},
{"admin users", func() error { return cliAdmin([]string{"users", "list"}) }, "alice@example.com"},
{"admin users delete", func() error { return cliAdmin([]string{"users", "delete", "bob"}) }, ""},
{"admin repos", func() error { return cliAdmin([]string{"repos", "list"}) }, "alice/demo"},
{"admin storage", func() error { return cliAdmin([]string{"storage", "check"}) }, "demo.git"},
}
for _, tc := range checks {
t.Run(tc.name, func(t *testing.T) {
out := captureStdout(t, func() {
if err := tc.fn(); err != nil {
t.Fatal(err)
}
})
if !strings.Contains(out, tc.want) {
t.Fatalf("output missing %q: %s", tc.want, out)
}
})
}
}