mirror of
https://github.com/chenasraf/cospend-cli.git
synced 2026-05-17 17:38:04 +00:00
refactor: extract url normalization to util
This commit is contained in:
52
cmd/info.go
Normal file
52
cmd/info.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/chenasraf/cospend-cli/internal/api"
|
||||
"github.com/chenasraf/cospend-cli/internal/cache"
|
||||
"github.com/chenasraf/cospend-cli/internal/config"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewInfoCommand creates the info command
|
||||
func NewInfoCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "info",
|
||||
Short: "Show account and configuration info",
|
||||
Long: `Show the configured Nextcloud server, authenticated user, and user locale/language.`,
|
||||
RunE: runInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func runInfo(cmd *cobra.Command, _ []string) error {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := api.NewClient(cfg)
|
||||
client.Debug = Debug
|
||||
client.DebugWriter = cmd.ErrOrStderr()
|
||||
|
||||
userInfo, ok := cache.LoadUserInfo()
|
||||
if !ok {
|
||||
userInfo, err = client.GetUserInfo()
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetching user info: %w", err)
|
||||
}
|
||||
_ = cache.SaveUserInfo(userInfo)
|
||||
}
|
||||
|
||||
server := config.NormalizeURL(cfg.Domain)
|
||||
|
||||
out := cmd.OutOrStdout()
|
||||
_, _ = fmt.Fprintf(out, "Server: %s\n", server)
|
||||
_, _ = fmt.Fprintf(out, "User: %s\n", cfg.User)
|
||||
_, _ = fmt.Fprintf(out, "Locale: %s\n", userInfo.Locale)
|
||||
_, _ = fmt.Fprintf(out, "Language: %s\n", userInfo.Language)
|
||||
|
||||
return nil
|
||||
}
|
||||
105
cmd/info_test.go
Normal file
105
cmd/info_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInfoCommand(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/ocs/v2.php/cloud/user" {
|
||||
_ = json.NewEncoder(w).Encode(makeOCSResponse(200, map[string]string{
|
||||
"locale": "he_IL",
|
||||
"language": "he",
|
||||
}))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
cleanup := setupTestEnv(t, server.URL)
|
||||
defer cleanup()
|
||||
|
||||
cmd := NewInfoCommand()
|
||||
var stdout bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
|
||||
err := cmd.Execute()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
output := stdout.String()
|
||||
|
||||
expected := []string{
|
||||
"Server: " + server.URL,
|
||||
"User: testuser",
|
||||
"Locale: he_IL",
|
||||
"Language: he",
|
||||
}
|
||||
for _, exp := range expected {
|
||||
if !strings.Contains(output, exp) {
|
||||
t.Errorf("Output missing %q, got:\n%s", exp, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfoCommandNormalizesURL(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/ocs/v2.php/cloud/user" {
|
||||
_ = json.NewEncoder(w).Encode(makeOCSResponse(200, map[string]string{
|
||||
"locale": "en_US",
|
||||
"language": "en",
|
||||
}))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Test with trailing slash — should be stripped
|
||||
cleanup := setupTestEnv(t, server.URL+"/")
|
||||
defer cleanup()
|
||||
|
||||
cmd := NewInfoCommand()
|
||||
var stdout bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
|
||||
err := cmd.Execute()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
output := stdout.String()
|
||||
if !strings.Contains(output, "Server: "+server.URL) {
|
||||
t.Errorf("Expected trailing slash stripped, got:\n%s", output)
|
||||
}
|
||||
if strings.Contains(output, server.URL+"/") {
|
||||
t.Errorf("Trailing slash should be stripped, got:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfoCommandAPIError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte("Internal Server Error"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
cleanup := setupTestEnv(t, server.URL)
|
||||
defer cleanup()
|
||||
|
||||
cmd := NewInfoCommand()
|
||||
var stdout bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
|
||||
err := cmd.Execute()
|
||||
if err == nil {
|
||||
t.Error("Expected error from API")
|
||||
}
|
||||
}
|
||||
@@ -76,13 +76,7 @@ func runInit(cmd *cobra.Command, _ []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domain = strings.TrimRight(domain, "/")
|
||||
|
||||
// Auto-prepend https:// if no scheme provided
|
||||
domainLower := strings.ToLower(domain)
|
||||
if !strings.HasPrefix(domainLower, "http://") && !strings.HasPrefix(domainLower, "https://") {
|
||||
domain = "https://" + domain
|
||||
}
|
||||
domain = config.NormalizeURL(domain)
|
||||
|
||||
// Choose login method
|
||||
_, _ = fmt.Fprintln(cmd.OutOrStdout())
|
||||
|
||||
@@ -262,13 +262,7 @@ func TestDomainAutoPrependHTTPS(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
domain := tt.input
|
||||
domain = strings.TrimRight(domain, "/")
|
||||
domainLower := strings.ToLower(domain)
|
||||
if !strings.HasPrefix(domainLower, "http://") && !strings.HasPrefix(domainLower, "https://") {
|
||||
domain = "https://" + domain
|
||||
}
|
||||
|
||||
domain := config.NormalizeURL(tt.input)
|
||||
if domain != tt.expected {
|
||||
t.Errorf("Domain = %s, want %s", domain, tt.expected)
|
||||
}
|
||||
|
||||
@@ -216,10 +216,7 @@ func (c *Client) debugf(format string, args ...interface{}) {
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(method, path string, body io.Reader) (*http.Response, error) {
|
||||
baseURL := strings.TrimSuffix(c.config.Domain, "/")
|
||||
if !strings.HasPrefix(baseURL, "http://") && !strings.HasPrefix(baseURL, "https://") {
|
||||
baseURL = "https://" + baseURL
|
||||
}
|
||||
baseURL := config.NormalizeURL(c.config.Domain)
|
||||
fullURL := fmt.Sprintf("%s%s", baseURL, path)
|
||||
|
||||
c.debugf("Request: %s %s", method, fullURL)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/adrg/xdg"
|
||||
@@ -14,6 +15,16 @@ import (
|
||||
|
||||
const appName = "cospend"
|
||||
|
||||
// NormalizeURL trims trailing slashes and prepends https:// if no scheme is present.
|
||||
func NormalizeURL(url string) string {
|
||||
url = strings.TrimRight(url, "/")
|
||||
lower := strings.ToLower(url)
|
||||
if !strings.HasPrefix(lower, "http://") && !strings.HasPrefix(lower, "https://") {
|
||||
url = "https://" + url
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
// Config holds the Nextcloud configuration
|
||||
type Config struct {
|
||||
Domain string `json:"domain" yaml:"domain" toml:"domain"`
|
||||
|
||||
1
main.go
1
main.go
@@ -26,6 +26,7 @@ func main() {
|
||||
rootCmd.AddCommand(cmd.NewListCommand())
|
||||
rootCmd.AddCommand(cmd.NewDeleteCommand())
|
||||
rootCmd.AddCommand(cmd.NewProjectsCommand())
|
||||
rootCmd.AddCommand(cmd.NewInfoCommand())
|
||||
|
||||
rootCmd.PersistentFlags().BoolVarP(&cmd.Debug, "debug", "d", false, "Enable debug output")
|
||||
rootCmd.PersistentFlags().StringVarP(&cmd.ProjectID, "project", "p", "", "Project ID")
|
||||
|
||||
Reference in New Issue
Block a user