package main import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "os" "strings" "testing" ) func TestFormatSize(t *testing.T) { tests := []struct { bytes int64 want string }{ {0, "0 B"}, {512, "512 B"}, {1024, "1.0 KB"}, {1536, "1.5 KB"}, {1048576, "1.0 MB"}, {1073741824, "1.0 GB"}, {1099511627776, "1.0 TB"}, {2684354560, "2.5 GB"}, } for _, tt := range tests { got := formatSize(tt.bytes) if got != tt.want { t.Errorf("formatSize(%d) = %q, want %q", tt.bytes, got, tt.want) } } } func TestPrintJSON(t *testing.T) { // Capture stdout. old := os.Stdout r, w, err := os.Pipe() if err != nil { t.Fatal(err) } os.Stdout = w data := map[string]string{"status": "ok"} if err := printJSON(data); err != nil { t.Fatal(err) } _ = w.Close() os.Stdout = old var buf bytes.Buffer if _, err := buf.ReadFrom(r); err != nil { t.Fatal(err) } var parsed map[string]string if err := json.Unmarshal(buf.Bytes(), &parsed); err != nil { t.Fatalf("printJSON output is not valid JSON: %v\nOutput: %s", err, buf.String()) } if parsed["status"] != "ok" { t.Errorf("expected status=ok, got %q", parsed["status"]) } // Verify indentation. if !strings.Contains(buf.String(), " ") { t.Error("expected indented JSON output") } } func TestPrintTable(t *testing.T) { var buf bytes.Buffer headers := []string{"NAME", "SIZE", "COUNT"} rows := [][]string{ {"alpha", "1.0 MB", "5"}, {"beta", "2.5 GB", "12"}, } printTable(&buf, headers, rows) output := buf.String() lines := strings.Split(strings.TrimSpace(output), "\n") if len(lines) != 3 { t.Fatalf("expected 3 lines (header + 2 rows), got %d:\n%s", len(lines), output) } // Header should contain all column names. for _, h := range headers { if !strings.Contains(lines[0], h) { t.Errorf("header missing %q: %s", h, lines[0]) } } // Rows should contain the data. if !strings.Contains(lines[1], "alpha") { t.Errorf("row 1 missing 'alpha': %s", lines[1]) } if !strings.Contains(lines[2], "beta") { t.Errorf("row 2 missing 'beta': %s", lines[2]) } } func TestTokenFromEnv(t *testing.T) { t.Setenv("MCR_TOKEN", "test-env-token") // Build client with empty token flag (should fall back to env). c, err := newClient("https://example.com", "", "", "") if err != nil { t.Fatal(err) } defer c.close() // The newClient does not resolve env; the main() PersistentPreRunE does. // So here we test the pattern manually. token := "" if token == "" { token = os.Getenv("MCR_TOKEN") } if token != "test-env-token" { t.Errorf("expected token from env, got %q", token) } } func TestTokenFlagOverridesEnv(t *testing.T) { t.Setenv("MCR_TOKEN", "env-token") flagVal := "flag-token" token := flagVal if token == "" { token = os.Getenv("MCR_TOKEN") } if token != "flag-token" { t.Errorf("expected flag token to override env, got %q", token) } } func TestNewClientREST(t *testing.T) { c, err := newClient("https://example.com", "", "mytoken", "") if err != nil { t.Fatal(err) } defer c.close() if c.useGRPC() { t.Error("expected REST client (no gRPC)") } if c.serverURL != "https://example.com" { t.Errorf("unexpected serverURL: %s", c.serverURL) } if c.token != "mytoken" { t.Errorf("unexpected token: %s", c.token) } } func TestNewClientBadCACert(t *testing.T) { _, err := newClient("https://example.com", "", "", "/nonexistent/ca.pem") if err == nil { t.Fatal("expected error for nonexistent CA cert") } if !strings.Contains(err.Error(), "CA cert") { t.Errorf("unexpected error: %v", err) } } func TestNewClientInvalidCACert(t *testing.T) { tmpFile := t.TempDir() + "/bad.pem" if err := os.WriteFile(tmpFile, []byte("not a certificate"), 0o600); err != nil { t.Fatal(err) } _, err := newClient("https://example.com", "", "", tmpFile) if err == nil { t.Fatal("expected error for invalid CA cert") } if !strings.Contains(err.Error(), "no valid certificates") { t.Errorf("unexpected error: %v", err) } } func TestRestDoSuccess(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Authorization") != "Bearer test-token" { t.Errorf("missing or wrong Authorization header: %s", r.Header.Get("Authorization")) } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"status":"ok"}`)) })) defer srv.Close() c := &apiClient{ serverURL: srv.URL, token: "test-token", httpClient: srv.Client(), } data, err := c.restDo("GET", "/v1/health", nil) if err != nil { t.Fatal(err) } var resp struct { Status string `json:"status"` } if err := json.Unmarshal(data, &resp); err != nil { t.Fatal(err) } if resp.Status != "ok" { t.Errorf("expected status=ok, got %q", resp.Status) } } func TestRestDoError(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusForbidden) _, _ = w.Write([]byte(`{"error":"admin role required"}`)) })) defer srv.Close() c := &apiClient{ serverURL: srv.URL, token: "bad-token", httpClient: srv.Client(), } _, err := c.restDo("GET", "/v1/repositories", nil) if err == nil { t.Fatal("expected error for 403 response") } if !strings.Contains(err.Error(), "admin role required") { t.Errorf("error should contain server message: %v", err) } if !strings.Contains(err.Error(), "403") { t.Errorf("error should contain status code: %v", err) } } func TestRestDoPostBody(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { t.Errorf("expected POST, got %s", r.Method) } if r.Header.Get("Content-Type") != "application/json" { t.Errorf("expected application/json content type, got %s", r.Header.Get("Content-Type")) } var body map[string]any if err := json.NewDecoder(r.Body).Decode(&body); err != nil { t.Errorf("failed to decode body: %v", err) } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte(`{"id":"gc-123"}`)) })) defer srv.Close() c := &apiClient{ serverURL: srv.URL, token: "token", httpClient: srv.Client(), } data, err := c.restDo("POST", "/v1/gc", map[string]string{"test": "value"}) if err != nil { t.Fatal(err) } if !strings.Contains(string(data), "gc-123") { t.Errorf("unexpected response: %s", string(data)) } }