package updater import ( "bytes" "context" "io" "net/http" "net/url" "testing" "github.com/Snider/Borg/pkg/mocks" ) func TestGithub_GetPublicRepos_Good(t *testing.T) { mockClient := mocks.NewMockClient(map[string]*http.Response{ "https://api.github.com/users/testuser/repos": { StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}, Body: io.NopCloser(bytes.NewBufferString(`[{"clone_url": "https://github.com/testuser/repo1.git"}]`)), }, "https://api.github.com/orgs/testorg/repos": { StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}, "Link": []string{`; rel="next"`}}, Body: io.NopCloser(bytes.NewBufferString(`[{"clone_url": "https://github.com/testorg/repo1.git"}]`)), }, "https://api.github.com/organizations/123/repos?page=2": { StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}, Body: io.NopCloser(bytes.NewBufferString(`[{"clone_url": "https://github.com/testorg/repo2.git"}]`)), }, }) client := &githubClient{} originalNewAuthenticatedClient := NewAuthenticatedClient NewAuthenticatedClient = func(ctx context.Context) *http.Client { return mockClient } defer func() { NewAuthenticatedClient = originalNewAuthenticatedClient }() // Test user repos repos, err := client.getPublicReposWithAPIURL(context.Background(), "https://api.github.com", "testuser") if err != nil { t.Fatalf("getPublicReposWithAPIURL for user failed: %v", err) } if len(repos) != 1 || repos[0] != "https://github.com/testuser/repo1.git" { t.Errorf("unexpected user repos: %v", repos) } // Test org repos with pagination repos, err = client.getPublicReposWithAPIURL(context.Background(), "https://api.github.com", "testorg") if err != nil { t.Fatalf("getPublicReposWithAPIURL for org failed: %v", err) } if len(repos) != 2 || repos[0] != "https://github.com/testorg/repo1.git" || repos[1] != "https://github.com/testorg/repo2.git" { t.Errorf("unexpected org repos: %v", repos) } } func TestGithub_GetPublicRepos_Bad(t *testing.T) { u, _ := url.Parse("https://api.github.com/users/testuser/repos") mockClient := mocks.NewMockClient(map[string]*http.Response{ "https://api.github.com/users/testuser/repos": { StatusCode: http.StatusNotFound, Status: "404 Not Found", Header: http.Header{"Content-Type": []string{"application/json"}}, Body: io.NopCloser(bytes.NewBufferString("")), Request: &http.Request{Method: "GET", URL: u}, }, "https://api.github.com/orgs/testuser/repos": { StatusCode: http.StatusNotFound, Status: "404 Not Found", Header: http.Header{"Content-Type": []string{"application/json"}}, Body: io.NopCloser(bytes.NewBufferString("")), Request: &http.Request{Method: "GET", URL: u}, }, }) expectedErr := "github.getPublicReposWithAPIURL: failed to fetch repos: 404 Not Found" client := &githubClient{} originalNewAuthenticatedClient := NewAuthenticatedClient NewAuthenticatedClient = func(ctx context.Context) *http.Client { return mockClient } defer func() { NewAuthenticatedClient = originalNewAuthenticatedClient }() _, err := client.getPublicReposWithAPIURL(context.Background(), "https://api.github.com", "testuser") if err == nil || err.Error() != expectedErr { t.Fatalf("expected %q, got %q", expectedErr, err) } } func TestGithub_GetPublicRepos_Ugly(t *testing.T) { // Context already cancelled — loop should terminate immediately ctx, cancel := context.WithCancel(context.Background()) cancel() mockClient := mocks.NewMockClient(map[string]*http.Response{}) client := &githubClient{} originalNewAuthenticatedClient := NewAuthenticatedClient NewAuthenticatedClient = func(ctx context.Context) *http.Client { return mockClient } defer func() { NewAuthenticatedClient = originalNewAuthenticatedClient }() _, err := client.getPublicReposWithAPIURL(ctx, "https://api.github.com", "testuser") if err == nil { t.Error("expected error from cancelled context, got nil") } } func TestGithub_FindNextURL_Good(t *testing.T) { client := &githubClient{} linkHeader := `; rel="next", ; rel="prev"` nextURL := client.findNextURL(linkHeader) if nextURL != "https://api.github.com/organizations/123/repos?page=2" { t.Errorf("unexpected next URL: %s", nextURL) } } func TestGithub_FindNextURL_Bad(t *testing.T) { client := &githubClient{} linkHeader := `; rel="prev"` nextURL := client.findNextURL(linkHeader) if nextURL != "" { t.Errorf("expected empty string for no next link, got %q", nextURL) } } func TestGithub_FindNextURL_Ugly(t *testing.T) { client := &githubClient{} // Empty header if got := client.findNextURL(""); got != "" { t.Errorf("expected empty string for empty header, got %q", got) } // Malformed header — no rel attribute if got := client.findNextURL(``); got != "" { t.Errorf("expected empty string for malformed header, got %q", got) } } func TestGithub_NewAuthenticatedClient_Good(t *testing.T) { // With token set — should return an authenticated (non-default) client t.Setenv("GITHUB_TOKEN", "test-token") client := NewAuthenticatedClient(context.Background()) if client == http.DefaultClient { t.Error("expected authenticated client, got http.DefaultClient") } } func TestGithub_NewAuthenticatedClient_Bad(t *testing.T) { // Without token — should return a plain (unauthenticated) HTTP client, not http.DefaultClient t.Setenv("GITHUB_TOKEN", "") client := NewAuthenticatedClient(context.Background()) if client == nil { t.Error("expected non-nil client when no token set") } } func TestGithub_NewAuthenticatedClient_Ugly(t *testing.T) { // Cancelled context still returns a client (client creation does not fail on cancelled context) ctx, cancel := context.WithCancel(context.Background()) cancel() client := NewAuthenticatedClient(ctx) if client == nil { t.Error("expected non-nil client even with cancelled context") } }