package woodpecker import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" ) func TestNewClientValidation(t *testing.T) { if _, err := NewClient("", "tok"); err == nil { t.Error("expected error for empty base URL") } if _, err := NewClient("https://example.com/api", ""); err == nil { t.Error("expected error for empty token") } } func TestNewClientBaseURL(t *testing.T) { c, err := NewClient("https://woodpecker.example.com/api/", "tok") if err != nil { t.Fatal(err) } if c.baseURL != "https://woodpecker.example.com/api" { t.Errorf("baseURL = %q, want trailing slash trimmed", c.baseURL) } } func TestGetSuccess(t *testing.T) { var gotPath, gotAuth string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotPath = r.URL.Path gotAuth = r.Header.Get("Authorization") w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"id":42,"name":"demo","owner":"me","full_name":"me/demo","private":true,"trusted":false,"active":true,"forge_id":1}`)) })) defer srv.Close() c, err := NewClient(srv.URL+"/api", "secret", WithHTTPClient(srv.Client())) if err != nil { t.Fatal(err) } var repo Repo if err := c.Get(context.Background(), "/repos/42", &repo); err != nil { t.Fatal(err) } if gotPath != "/api/repos/42" { t.Errorf("path = %q", gotPath) } if gotAuth != "Bearer secret" { t.Errorf("auth header = %q", gotAuth) } if repo.ID != 42 || repo.FullName != "me/demo" || !repo.Private { t.Errorf("unexpected repo: %+v", repo) } } func TestGetEmptyBodySkipsDecode(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) })) defer srv.Close() c, _ := NewClient(srv.URL+"/api", "t", WithHTTPClient(srv.Client())) var out Repo if err := c.Get(context.Background(), "/repos/1", &out); err != nil { t.Errorf("empty body should not fail: %v", err) } } func TestGetErrorStatusAndMessage(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNotFound) _, _ = w.Write([]byte(`{"error":"repository not found"}`)) })) defer srv.Close() c, _ := NewClient(srv.URL+"/api", "t", WithHTTPClient(srv.Client())) err := c.Get(context.Background(), "/repos/999", &struct{}{}) apiErr, ok := err.(*APIError) if !ok { t.Fatalf("expected *APIError, got %T: %v", err, err) } if apiErr.Status != 404 { t.Errorf("status = %d, want 404", apiErr.Status) } if apiErr.Message != "repository not found" { t.Errorf("message = %q", apiErr.Message) } } func TestGetNetworkErrorBecomes500(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})) url := srv.URL srv.Close() // connections will fail now c, _ := NewClient(url+"/api", "t", WithHTTPClient(srv.Client())) err := c.Get(context.Background(), "/repos/1", &struct{}{}) apiErr, ok := err.(*APIError) if !ok { t.Fatalf("expected *APIError, got %T: %v", err, err) } if apiErr.Status != 500 { t.Errorf("status = %d, want 500 for transport error", apiErr.Status) } if apiErr.Message == "" { t.Error("message must carry the transport error detail") } } func TestGetInvalidJSONBecomes500(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte(`{not json`)) })) defer srv.Close() c, _ := NewClient(srv.URL+"/api", "t", WithHTTPClient(srv.Client())) err := c.Get(context.Background(), "/repos/1", &Repo{}) apiErr, ok := err.(*APIError) if !ok { t.Fatalf("expected *APIError, got %T: %v", err, err) } if apiErr.Status != 500 { t.Errorf("status = %d, want 500", apiErr.Status) } } func TestErrorTypeUnmarshalsWithPointers(t *testing.T) { var p Pipeline payload := `{"id":1,"number":5,"status":"running","event":"push","commit":"abc","branch":"main","ref":"refs/heads/main","created_at":10,"updated_at":11,"started_at":null,"message":null}` if err := json.Unmarshal([]byte(payload), &p); err != nil { t.Fatal(err) } if p.Message != nil { t.Error("message should stay nil for explicit null") } if p.StartedAt != nil { t.Error("started_at should stay nil for explicit null") } if p.Branch != "main" { t.Errorf("branch = %q", p.Branch) } if !strings.HasPrefix(payload, "{") { t.Fatal("sanity") } }