Skip to content

Commit 3e8e3ae

Browse files
authored
refactor(librarian): Inject client factories and add test for generate default - 2 (#1923)
This PR refactors `newCommandRunner` in `command.go` to accept `GitHubClientFactory` and `ContainerClientFactory` as arguments, improving testability by enabling direct injection of mock clients. Key changes include: * Defined `GitHubClientFactory` and `ContainerClientFactory` types in `command.go`. * Updated `newCommandRunner` to use these factories, with default implementations if `nil` is passed. * Modified callers of `newCommandRunner` (e.g., in `generate.go`, `release_init.go`, `tag_and_release.go`) to pass `nil, nil` for the new factory arguments to retain default behavior. * Added `TestGenerate_DefaultBehavior` to `librarian_test.go` to test the default `librarian generate` execution. This test utilizes the new factory arguments in `newGenerateRunner` to inject mock clients. This was attempted in #1918 but rolled back in #1919 due to slowness in tests. This is fixed in this commit. Fixes #1906
1 parent 52bdac8 commit 3e8e3ae

File tree

6 files changed

+75
-9
lines changed

6 files changed

+75
-9
lines changed

internal/librarian/command.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ const (
4141
release = "release"
4242
)
4343

44+
// GitHubClientFactory type for creating a GitHubClient.
45+
type GitHubClientFactory func(token string, repo *github.Repository) (GitHubClient, error)
46+
47+
// ContainerClientFactory type for creating a ContainerClient.
48+
type ContainerClientFactory func(workRoot, image, userUID, userGID string) (ContainerClient, error)
49+
4450
type commitInfo struct {
4551
cfg *config.Config
4652
state *config.LibrarianState
@@ -66,7 +72,20 @@ type commandRunner struct {
6672

6773
const defaultAPISourceBranch = "master"
6874

69-
func newCommandRunner(cfg *config.Config) (*commandRunner, error) {
75+
func newCommandRunner(cfg *config.Config, ghClientFactory GitHubClientFactory, containerClientFactory ContainerClientFactory) (*commandRunner, error) {
76+
// If no GitHub client factory is provided, use the default one.
77+
if ghClientFactory == nil {
78+
ghClientFactory = func(token string, repo *github.Repository) (GitHubClient, error) {
79+
return github.NewClient(token, repo)
80+
}
81+
}
82+
// If no container client factory is provided, use the default one.
83+
if containerClientFactory == nil {
84+
containerClientFactory = func(workRoot, image, userUID, userGID string) (ContainerClient, error) {
85+
return docker.New(workRoot, image, userUID, userGID)
86+
}
87+
}
88+
7089
if cfg.APISource == "" {
7190
cfg.APISource = "https://github.com/googleapis/googleapis"
7291
}
@@ -109,12 +128,12 @@ func newCommandRunner(cfg *config.Config) (*commandRunner, error) {
109128
return nil, fmt.Errorf("failed to get GitHub repo from remote: %w", err)
110129
}
111130
}
112-
ghClient, err := github.NewClient(cfg.GitHubToken, gitRepo)
131+
ghClient, err := ghClientFactory(cfg.GitHubToken, gitRepo)
113132
if err != nil {
114133
return nil, fmt.Errorf("failed to create GitHub client: %w", err)
115134
}
116135

117-
container, err := docker.New(cfg.WorkRoot, image, cfg.UserUID, cfg.UserGID)
136+
container, err := containerClientFactory(cfg.WorkRoot, image, cfg.UserUID, cfg.UserGID)
118137
if err != nil {
119138
return nil, err
120139
}

internal/librarian/generate.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ After generation, if the "-push" flag is provided, the changes are committed to
6262
a pull request is created. Otherwise, the changes are left in the local working tree for
6363
inspection.`,
6464
Run: func(ctx context.Context, cfg *config.Config) error {
65-
runner, err := newGenerateRunner(cfg)
65+
runner, err := newGenerateRunner(cfg, nil, nil)
6666
if err != nil {
6767
return err
6868
}
@@ -98,8 +98,8 @@ type generateRunner struct {
9898
image string
9999
}
100100

101-
func newGenerateRunner(cfg *config.Config) (*generateRunner, error) {
102-
runner, err := newCommandRunner(cfg)
101+
func newGenerateRunner(cfg *config.Config, ghClientFactory GitHubClientFactory, containerClientFactory ContainerClientFactory) (*generateRunner, error) {
102+
runner, err := newCommandRunner(cfg, ghClientFactory, containerClientFactory)
103103
if err != nil {
104104
return nil, err
105105
}

internal/librarian/generate_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ func TestNewGenerateRunner(t *testing.T) {
504504
}
505505
}
506506

507-
r, err := newGenerateRunner(test.cfg)
507+
r, err := newGenerateRunner(test.cfg, nil, nil)
508508
if (err != nil) != test.wantErr {
509509
t.Errorf("newGenerateRunner() error = %v, wantErr %v", err, test.wantErr)
510510
}

internal/librarian/librarian_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030

3131
"github.com/googleapis/librarian/internal/cli"
3232
"github.com/googleapis/librarian/internal/config"
33+
"github.com/googleapis/librarian/internal/github"
3334
"github.com/googleapis/librarian/internal/gitrepo"
3435
"gopkg.in/yaml.v3"
3536

@@ -82,6 +83,52 @@ func TestParentCommands(t *testing.T) {
8283
}
8384
}
8485

86+
func TestGenerate_DefaultBehavior(t *testing.T) {
87+
ctx := context.Background()
88+
89+
// 1. Setup a mock repository with a state file
90+
repo := newTestGitRepoWithState(t, true)
91+
repoDir := repo.GetDir()
92+
93+
// Setup a dummy API Source repo to prevent cloning googleapis/googleapis
94+
apiSourceDir := t.TempDir()
95+
runGit(t, apiSourceDir, "init")
96+
runGit(t, apiSourceDir, "config", "user.email", "[email protected]")
97+
runGit(t, apiSourceDir, "config", "user.name", "Test User")
98+
runGit(t, apiSourceDir, "commit", "--allow-empty", "-m", "initial commit")
99+
100+
t.Chdir(repoDir)
101+
102+
// 2. Override dependency creation to use mocks
103+
mockContainer := &mockContainerClient{
104+
wantLibraryGen: true,
105+
}
106+
mockGH := &mockGitHubClient{}
107+
108+
// 3. Call librarian.Run
109+
cfg := config.New("generate")
110+
cfg.WorkRoot = repoDir
111+
cfg.Repo = repoDir
112+
cfg.APISource = apiSourceDir
113+
runner, err := newGenerateRunner(cfg, func(token string, repo *github.Repository) (GitHubClient, error) {
114+
return mockGH, nil
115+
}, func(workRoot, image, userUID, userGID string) (ContainerClient, error) {
116+
return mockContainer, nil
117+
})
118+
if err != nil {
119+
t.Fatalf("newGenerateRunner() failed: %v", err)
120+
}
121+
if err := runner.run(ctx); err != nil {
122+
t.Fatalf("runner.run() failed: %v", err)
123+
}
124+
125+
// 4. Assertions
126+
expectedGenerateCalls := 1
127+
if mockContainer.generateCalls != expectedGenerateCalls {
128+
t.Errorf("Run(ctx, \"generate\"): got %d generate calls, want %d", mockContainer.generateCalls, expectedGenerateCalls)
129+
}
130+
}
131+
85132
func TestIsURL(t *testing.T) {
86133
for _, test := range []struct {
87134
name string

internal/librarian/release_init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ type initRunner struct {
7878
}
7979

8080
func newInitRunner(cfg *config.Config) (*initRunner, error) {
81-
runner, err := newCommandRunner(cfg)
81+
runner, err := newCommandRunner(cfg, nil, nil)
8282
if err != nil {
8383
return nil, fmt.Errorf("failed to create init runner: %w", err)
8484
}

internal/librarian/tag_and_release.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ type tagAndReleaseRunner struct {
7474
}
7575

7676
func newTagAndReleaseRunner(cfg *config.Config) (*tagAndReleaseRunner, error) {
77-
runner, err := newCommandRunner(cfg)
77+
runner, err := newCommandRunner(cfg, nil, nil)
7878
if err != nil {
7979
return nil, err
8080
}

0 commit comments

Comments
 (0)