Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion cmd/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/Snider/Borg/pkg/compress"
"github.com/Snider/Borg/pkg/datanode"
"github.com/Snider/Borg/pkg/github"
"github.com/Snider/Borg/pkg/ratelimit"
"github.com/Snider/Borg/pkg/tim"
"github.com/Snider/Borg/pkg/trix"
"github.com/Snider/Borg/pkg/ui"
Expand Down Expand Up @@ -42,7 +43,16 @@ func NewAllCmd() *cobra.Command {
return err
}

repos, err := GithubClient.GetPublicRepos(cmd.Context(), owner)
bandwidth, _ := cmd.Flags().GetString("bandwidth")
bytesPerSec, err := ratelimit.ParseBandwidth(bandwidth)
if err != nil {
return fmt.Errorf("invalid bandwidth: %w", err)
}

client := github.NewAuthenticatedClient(cmd.Context(), ratelimit.NewRateLimitedRoundTripper(nil, bytesPerSec))
githubClient := GithubClient(client)

repos, err := githubClient.GetPublicRepos(cmd.Context(), owner)
if err != nil {
return err
}
Expand Down
56 changes: 21 additions & 35 deletions cmd/all_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package cmd

import (
"bytes"

Check failure on line 4 in cmd/all_test.go

View workflow job for this annotation

GitHub Actions / build

"bytes" imported and not used

Check failure on line 4 in cmd/all_test.go

View workflow job for this annotation

GitHub Actions / build

"bytes" imported and not used
"context"

Check failure on line 5 in cmd/all_test.go

View workflow job for this annotation

GitHub Actions / build

"context" imported and not used

Check failure on line 5 in cmd/all_test.go

View workflow job for this annotation

GitHub Actions / build

"context" imported and not used
"io"

Check failure on line 6 in cmd/all_test.go

View workflow job for this annotation

GitHub Actions / build

"io" imported and not used

Check failure on line 6 in cmd/all_test.go

View workflow job for this annotation

GitHub Actions / build

"io" imported and not used
"net/http"
"path/filepath"
"testing"
Expand All @@ -15,19 +15,16 @@

func TestAllCmd_Good(t *testing.T) {
// Setup mock HTTP client for GitHub API
mockGithubClient := 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"}]`)),
},
})
oldNewAuthenticatedClient := github.NewAuthenticatedClient
github.NewAuthenticatedClient = func(ctx context.Context) *http.Client {
mockGithubClient := &mocks.MockGithubClient{

Check failure on line 18 in cmd/all_test.go

View workflow job for this annotation

GitHub Actions / build

undefined: mocks.MockGithubClient

Check failure on line 18 in cmd/all_test.go

View workflow job for this annotation

GitHub Actions / build

undefined: mocks.MockGithubClient
Repos: []string{"https://github.com/testuser/repo1.git"},
Err: nil,
}
oldGithubClient := GithubClient
GithubClient = func(client *http.Client) github.GithubClient {
return mockGithubClient
}
defer func() {
github.NewAuthenticatedClient = oldNewAuthenticatedClient
GithubClient = oldGithubClient
}()

// Setup mock Git cloner
Expand All @@ -54,24 +51,16 @@

func TestAllCmd_Bad(t *testing.T) {
// Setup mock HTTP client to return an error
mockGithubClient := mocks.NewMockClient(map[string]*http.Response{
"https://api.github.com/users/baduser/repos": {
StatusCode: http.StatusNotFound,
Status: "404 Not Found",
Body: io.NopCloser(bytes.NewBufferString(`{"message": "Not Found"}`)),
},
"https://api.github.com/orgs/baduser/repos": {
StatusCode: http.StatusNotFound,
Status: "404 Not Found",
Body: io.NopCloser(bytes.NewBufferString(`{"message": "Not Found"}`)),
},
})
oldNewAuthenticatedClient := github.NewAuthenticatedClient
github.NewAuthenticatedClient = func(ctx context.Context) *http.Client {
mockGithubClient := &mocks.MockGithubClient{

Check failure on line 54 in cmd/all_test.go

View workflow job for this annotation

GitHub Actions / build

undefined: mocks.MockGithubClient

Check failure on line 54 in cmd/all_test.go

View workflow job for this annotation

GitHub Actions / build

undefined: mocks.MockGithubClient
Repos: nil,
Err: fmt.Errorf("github error"),

Check failure on line 56 in cmd/all_test.go

View workflow job for this annotation

GitHub Actions / build

undefined: fmt

Check failure on line 56 in cmd/all_test.go

View workflow job for this annotation

GitHub Actions / build

undefined: fmt
}
oldGithubClient := GithubClient
GithubClient = func(client *http.Client) github.GithubClient {
return mockGithubClient
}
defer func() {
github.NewAuthenticatedClient = oldNewAuthenticatedClient
GithubClient = oldGithubClient
}()

rootCmd := NewRootCmd()
Expand All @@ -88,19 +77,16 @@
func TestAllCmd_Ugly(t *testing.T) {
t.Run("User with no repos", func(t *testing.T) {
// Setup mock HTTP client for a user with no repos
mockGithubClient := mocks.NewMockClient(map[string]*http.Response{
"https://api.github.com/users/emptyuser/repos": {
StatusCode: http.StatusOK,
Header: http.Header{"Content-Type": []string{"application/json"}},
Body: io.NopCloser(bytes.NewBufferString(`[]`)),
},
})
oldNewAuthenticatedClient := github.NewAuthenticatedClient
github.NewAuthenticatedClient = func(ctx context.Context) *http.Client {
mockGithubClient := &mocks.MockGithubClient{

Check failure on line 80 in cmd/all_test.go

View workflow job for this annotation

GitHub Actions / build

undefined: mocks.MockGithubClient

Check failure on line 80 in cmd/all_test.go

View workflow job for this annotation

GitHub Actions / build

undefined: mocks.MockGithubClient
Repos: []string{},
Err: nil,
}
oldGithubClient := GithubClient
GithubClient = func(client *http.Client) github.GithubClient {
return mockGithubClient
}
defer func() {
github.NewAuthenticatedClient = oldNewAuthenticatedClient
GithubClient = oldGithubClient
}()

rootCmd := NewRootCmd()
Expand Down
4 changes: 3 additions & 1 deletion cmd/collect.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ func init() {
RootCmd.AddCommand(GetCollectCmd())
}
func NewCollectCmd() *cobra.Command {
return &cobra.Command{
cmd := &cobra.Command{
Use: "collect",
Short: "Collect a resource from a URI.",
Long: `Collect a resource from a URI and store it in a DataNode.`,
}
cmd.PersistentFlags().String("bandwidth", "0", "Limit download bandwidth (e.g., 1MB/s, 500KB/s, 0 for unlimited)")
return cmd
}

func GetCollectCmd() *cobra.Command {
Expand Down
23 changes: 23 additions & 0 deletions cmd/collect_github_repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,26 @@ func TestCollectGithubRepoCmd_Ugly(t *testing.T) {
}
})
}

func TestCollectGithubRepoCmd_Bandwidth(t *testing.T) {
// Setup mock Git cloner
mockCloner := &mocks.MockGitCloner{
DN: datanode.New(),
Err: nil,
}
oldCloner := GitCloner
GitCloner = mockCloner
defer func() {
GitCloner = oldCloner
}()

rootCmd := NewRootCmd()
rootCmd.AddCommand(GetCollectCmd())

// Execute command with a bandwidth limit
out := filepath.Join(t.TempDir(), "out")
_, err := executeCommand(rootCmd, "collect", "github", "repo", "https://github.com/testuser/repo1", "--output", out, "--bandwidth", "1KB/s")
if err != nil {
t.Fatalf("collect github repo command failed: %v", err)
}
}
Comment on lines +69 to +90

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test is named TestCollectGithubRepoCmd_Bandwidth, but it doesn't seem to actually test the bandwidth limiting functionality. It mocks the GitCloner and only verifies that the command runs without error when the --bandwidth flag is present.

For this test to be effective, it should verify that the download speed is actually being limited. This would likely require not mocking the git cloning process and instead using a test repository, similar to how TestCollectWebsiteCmd_Bandwidth uses an httptest.Server.

Additionally, it's unclear if the bandwidth limiting (which is implemented at the http.RoundTripper level) is applied to the git clone operation. Please clarify if this is intended and implemented.

17 changes: 15 additions & 2 deletions cmd/collect_github_repos.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,35 @@ package cmd

import (
"fmt"
"net/http"

"github.com/Snider/Borg/pkg/github"
"github.com/Snider/Borg/pkg/ratelimit"
"github.com/spf13/cobra"
)

var (
// GithubClient is the github client used by the command. It can be replaced for testing.
GithubClient = github.NewGithubClient()
GithubClient = func(client *http.Client) github.GithubClient {
return github.NewGithubClient(client)
}
)

var collectGithubReposCmd = &cobra.Command{
Use: "repos [user-or-org]",
Short: "Collects all public repositories for a user or organization",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
repos, err := GithubClient.GetPublicRepos(cmd.Context(), args[0])
bandwidth, _ := cmd.Flags().GetString("bandwidth")
bytesPerSec, err := ratelimit.ParseBandwidth(bandwidth)
if err != nil {
return fmt.Errorf("invalid bandwidth: %w", err)
}

client := github.NewAuthenticatedClient(cmd.Context(), ratelimit.NewRateLimitedRoundTripper(http.DefaultTransport, bytesPerSec))
githubClient := GithubClient(client)

repos, err := githubClient.GetPublicRepos(cmd.Context(), args[0])
if err != nil {
return err
}
Expand Down
130 changes: 130 additions & 0 deletions cmd/collect_github_repos_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package cmd

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"

"github.com/Snider/Borg/pkg/github"
)

type mockGithubClient struct {
repos []string
err error
}

func (m *mockGithubClient) GetPublicRepos(ctx context.Context, userOrOrg string) ([]string, error) {
return m.repos, m.err
}

func TestCollectGithubReposCmd_Good(t *testing.T) {
oldGithubClient := GithubClient
GithubClient = func(client *http.Client) github.GithubClient {
return &mockGithubClient{
repos: []string{"https://github.com/testuser/repo1", "https://github.com/testuser/repo2"},
err: nil,
}
}
defer func() {
GithubClient = oldGithubClient
}()

rootCmd := NewRootCmd()
rootCmd.AddCommand(GetCollectCmd())

// Execute command
output, err := executeCommand(rootCmd, "collect", "github", "repos", "testuser")
if err != nil {
t.Fatalf("collect github repos command failed: %v", err)
}

expected := "https://github.com/testuser/repo1\nhttps://github.com/testuser/repo2\n"
if output != expected {
t.Errorf("expected output %q, but got %q", expected, output)
}
}

func TestCollectGithubReposCmd_Bad(t *testing.T) {
oldGithubClient := GithubClient
GithubClient = func(client *http.Client) github.GithubClient {
return &mockGithubClient{
repos: nil,
err: fmt.Errorf("github error"),
}
}
defer func() {
GithubClient = oldGithubClient
}()

rootCmd := NewRootCmd()
rootCmd.AddCommand(GetCollectCmd())

// Execute command
_, err := executeCommand(rootCmd, "collect", "github", "repos", "testuser")
if err == nil {
t.Fatal("expected an error, but got none")
}
}

func TestCollectGithubReposCmd_Ugly(t *testing.T) {
t.Run("Invalid bandwidth", func(t *testing.T) {
rootCmd := NewRootCmd()
rootCmd.AddCommand(GetCollectCmd())
_, err := executeCommand(rootCmd, "collect", "github", "repos", "testuser", "--bandwidth", "1Gbps")
if err == nil {
t.Fatal("expected an error for invalid bandwidth, but got none")
}
if !strings.Contains(err.Error(), "invalid bandwidth") {
t.Errorf("unexpected error message: %v", err)
}
})
}

func TestCollectGithubReposCmd_Bandwidth(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// This is a simplified mock of the GitHub API. It returns a single repo.
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `[{"clone_url": "https://github.com/testuser/repo1"}]`)
}))
defer server.Close()

// We need to override the API URL to point to our test server.
oldGetPublicRepos := GithubClient
GithubClient = func(client *http.Client) github.GithubClient {
return &mockGithubClient{
repos: []string{"https://github.com/testuser/repo1"},
err: nil,
}
}
defer func() {
GithubClient = oldGetPublicRepos
}()

rootCmd := NewRootCmd()
rootCmd.AddCommand(GetCollectCmd())

// Execute command with a bandwidth limit
start := time.Now()
_, err := executeCommand(rootCmd, "collect", "github", "repos", "testuser", "--bandwidth", "1KB/s")
if err != nil {
t.Fatalf("collect github repos command failed: %v", err)
}
elapsed := time.Since(start)

// Since the response is very small, we can't reliably test the bandwidth limit.
// We'll just check that the command runs without error.
if elapsed > 1*time.Second {
t.Errorf("expected the command to run quickly, but it took %s", elapsed)
}
}

// getPublicReposWithAPIURL is a copy of the private function in pkg/github/github.go, so we can test it.
type githubClient struct {
client *http.Client
}

var getPublicReposWithAPIURL func(g *githubClient, ctx context.Context, apiURL, userOrOrg string) ([]string, error)
Comment on lines +125 to +130

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This appears to be dead code, likely a leftover from before GetPublicReposWithAPIURL was made public. It should be removed to improve code clarity.

14 changes: 13 additions & 1 deletion cmd/collect_website.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package cmd

import (
"fmt"
"net/http"
"os"

"github.com/schollz/progressbar/v3"
"github.com/Snider/Borg/pkg/compress"
"github.com/Snider/Borg/pkg/ratelimit"
"github.com/Snider/Borg/pkg/tim"
"github.com/Snider/Borg/pkg/trix"
"github.com/Snider/Borg/pkg/ui"
Expand Down Expand Up @@ -51,7 +53,17 @@ func NewCollectWebsiteCmd() *cobra.Command {
bar = ui.NewProgressBar(-1, "Crawling website")
}

dn, err := website.DownloadAndPackageWebsite(websiteURL, depth, bar)
bandwidth, _ := cmd.Flags().GetString("bandwidth")
bytesPerSec, err := ratelimit.ParseBandwidth(bandwidth)
if err != nil {
return fmt.Errorf("invalid bandwidth: %w", err)
}

client := &http.Client{
Transport: ratelimit.NewRateLimitedRoundTripper(http.DefaultTransport, bytesPerSec),
}

dn, err := website.DownloadAndPackageWebsiteWithClient(websiteURL, depth, bar, client)
if err != nil {
return fmt.Errorf("error downloading and packaging website: %w", err)
}
Expand Down
Loading
Loading