package api import ( "context" "encoding/json" "errors" "net/http" "net/http/httptest" "testing" ) func TestAuthHeaderAndPath(t *testing.T) { var gotAuth, gotPath, gotAccept string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotAuth = r.Header.Get("Authorization") gotPath = r.URL.Path gotAccept = r.Header.Get("Accept") json.NewEncoder(w).Encode(User{Handle: "ricktester"}) })) defer srv.Close() c := New(srv.URL, "rickub_pat_secret") u, err := c.GetUser(context.Background()) if err != nil { t.Fatalf("GetUser: %v", err) } if u.Handle != "ricktester" { t.Errorf("handle = %q", u.Handle) } if gotAuth != "Bearer rickub_pat_secret" { t.Errorf("auth header = %q", gotAuth) } if gotPath != "/api/v1/user" { t.Errorf("path = %q", gotPath) } if gotAccept != "application/json" { t.Errorf("accept = %q", gotAccept) } } func TestErrorEnvelopeParsed(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) w.Write([]byte(`{"error":{"code":"not_found","message":"repository not found"}}`)) })) defer srv.Close() c := New(srv.URL, "t") _, err := c.GetRepo(context.Background(), "who", "what") if err == nil { t.Fatal("expected error") } var apiErr *APIError if !errors.As(err, &apiErr) { t.Fatalf("expected *APIError, got %T", err) } if apiErr.Status != http.StatusNotFound { t.Errorf("status = %d", apiErr.Status) } if apiErr.Code != "not_found" { t.Errorf("code = %q", apiErr.Code) } if apiErr.Message != "repository not found" { t.Errorf("message = %q", apiErr.Message) } if apiErr.Error() != "repository not found (not_found)" { t.Errorf("Error() = %q", apiErr.Error()) } } func TestNonJSONErrorFallback(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadGateway) w.Write([]byte("upstream boom")) })) defer srv.Close() c := New(srv.URL, "t") _, err := c.GetUser(context.Background()) var apiErr *APIError if !errors.As(err, &apiErr) { t.Fatalf("expected *APIError, got %v", err) } if apiErr.Message != "upstream boom" { t.Errorf("message = %q", apiErr.Message) } } func TestPaginationQueryAndDecode(t *testing.T) { var gotQuery string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotQuery = r.URL.RawQuery json.NewEncoder(w).Encode(map[string]any{ "page": 2, "per_page": 5, "has_next": true, "items": []map[string]any{ {"owner": "ricktester", "name": "demo", "full_name": "ricktester/demo", "visibility": "public"}, }, }) })) defer srv.Close() c := New(srv.URL, "t") page, err := c.ListUserRepos(context.Background(), "ricktester", 2, 5) if err != nil { t.Fatalf("ListUserRepos: %v", err) } if gotQuery != "page=2&per_page=5" { t.Errorf("query = %q", gotQuery) } if page.Page.Page != 2 || page.PerPage != 5 || !page.HasNext { t.Errorf("page envelope = %+v", page.Page) } if len(page.Items) != 1 || page.Items[0].FullName != "ricktester/demo" { t.Errorf("items = %+v", page.Items) } } func TestSearchQueryEncoding(t *testing.T) { var gotQuery string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotQuery = r.URL.RawQuery json.NewEncoder(w).Encode(RepoPage{}) })) defer srv.Close() c := New(srv.URL, "t") if _, err := c.SearchRepos(context.Background(), "hello world", 0, 0); err != nil { t.Fatalf("SearchRepos: %v", err) } if gotQuery != "q=hello+world" { t.Errorf("query = %q", gotQuery) } } func TestCreateRepoSendsBody(t *testing.T) { var gotMethod string var body RepoCreate srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotMethod = r.Method json.NewDecoder(r.Body).Decode(&body) if ct := r.Header.Get("Content-Type"); ct != "application/json" { t.Errorf("content-type = %q", ct) } w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(Repo{FullName: "ricktester/demo", Visibility: "public"}) })) defer srv.Close() c := New(srv.URL, "t") r, err := c.CreateRepo(context.Background(), RepoCreate{Name: "demo", Visibility: "public"}) if err != nil { t.Fatalf("CreateRepo: %v", err) } if gotMethod != http.MethodPost { t.Errorf("method = %s", gotMethod) } if body.Name != "demo" || body.Visibility != "public" { t.Errorf("body = %+v", body) } if r.FullName != "ricktester/demo" { t.Errorf("full_name = %q", r.FullName) } } func TestDeleteRepoNoContent(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodDelete { t.Errorf("method = %s", r.Method) } w.WriteHeader(http.StatusNoContent) })) defer srv.Close() c := New(srv.URL, "t") if err := c.DeleteRepo(context.Background(), "o", "r"); err != nil { t.Fatalf("DeleteRepo: %v", err) } } func TestRawTextAcceptHeader(t *testing.T) { var gotAccept string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotAccept = r.Header.Get("Accept") w.Header().Set("Content-Type", "text/plain") w.Write([]byte("hello\n")) })) defer srv.Close() c := New(srv.URL, "t") data, err := c.GetRaw(context.Background(), "o", "r", "main", "README.md") if err != nil { t.Fatalf("GetRaw: %v", err) } if gotAccept != "text/plain" { t.Errorf("accept = %q", gotAccept) } if string(data) != "hello\n" { t.Errorf("data = %q", data) } } func TestContentsPathEscaping(t *testing.T) { var gotPath string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotPath = r.URL.EscapedPath() json.NewEncoder(w).Encode(Contents{Type: "file", File: &Blob{Path: "a/b.txt", Content: "hi"}}) })) defer srv.Close() c := New(srv.URL, "t") if _, err := c.GetContents(context.Background(), "o", "r", "main", "dir/sub/file name.txt"); err != nil { t.Fatalf("GetContents: %v", err) } // Each segment escaped, slashes preserved. want := "/api/v1/repos/o/r/contents/main/dir/sub/file%20name.txt" if gotPath != want { t.Errorf("path = %q, want %q", gotPath, want) } }