Skip to content

Commit 05a817b

Browse files
authored
chore(internal/librarian): extend configure contract to include version check (#1659)
Fixes #1010
1 parent 73f799b commit 05a817b

File tree

4 files changed

+104
-94
lines changed

4 files changed

+104
-94
lines changed

internal/librarian/generate.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,11 @@ func (r *generateRunner) runConfigureCommand(ctx context.Context) (string, error
558558
return "", err
559559
}
560560

561+
if libraryState.Version == "" {
562+
slog.Info("library doesn't receive a version, apply the default version", "id", r.cfg.Library)
563+
libraryState.Version = "0.0.0"
564+
}
565+
561566
// Update the library state in the librarian state.
562567
for i, library := range r.state.Libraries {
563568
if library.ID != libraryState.ID {

internal/librarian/generate_test.go

Lines changed: 87 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ package librarian
1717
import (
1818
"context"
1919
"errors"
20-
"fmt"
2120
"os"
2221
"os/exec"
2322
"path/filepath"
@@ -204,6 +203,7 @@ func TestRunConfigureCommand(t *testing.T) {
204203
container *mockContainerClient
205204
wantConfigureCalls int
206205
wantErr bool
206+
wantErrMsg string
207207
}{
208208
{
209209
name: "configures library successfully",
@@ -235,6 +235,7 @@ func TestRunConfigureCommand(t *testing.T) {
235235
container: &mockContainerClient{},
236236
wantConfigureCalls: 1,
237237
wantErr: true,
238+
wantErrMsg: "failed to read dir",
238239
},
239240
{
240241
name: "configures library with error message in response",
@@ -253,6 +254,7 @@ func TestRunConfigureCommand(t *testing.T) {
253254
},
254255
wantConfigureCalls: 1,
255256
wantErr: true,
257+
wantErrMsg: "failed with error message",
256258
},
257259
{
258260
name: "configures library with no response",
@@ -271,6 +273,24 @@ func TestRunConfigureCommand(t *testing.T) {
271273
},
272274
wantConfigureCalls: 1,
273275
wantErr: true,
276+
wantErrMsg: "failed to read response file",
277+
},
278+
{
279+
name: "configures library without initial version",
280+
api: "some/api",
281+
repo: newTestGitRepo(t),
282+
state: &config.LibrarianState{
283+
Libraries: []*config.LibraryState{
284+
{
285+
ID: "some-library",
286+
APIs: []*config.API{{Path: "some/api"}},
287+
},
288+
},
289+
},
290+
container: &mockContainerClient{
291+
noInitVersion: true,
292+
},
293+
wantConfigureCalls: 1,
274294
},
275295
{
276296
name: "configure command failed",
@@ -285,10 +305,12 @@ func TestRunConfigureCommand(t *testing.T) {
285305
},
286306
},
287307
container: &mockContainerClient{
288-
configureErr: errors.New("simulated configure command error"),
308+
configureErr: errors.New("simulated configure command error"),
309+
noConfigureResponse: true,
289310
},
290311
wantConfigureCalls: 1,
291312
wantErr: true,
313+
wantErrMsg: "simulated configure command error",
292314
},
293315
} {
294316
t.Run(test.name, func(t *testing.T) {
@@ -305,30 +327,22 @@ func TestRunConfigureCommand(t *testing.T) {
305327
containerClient: test.container,
306328
}
307329

308-
if test.name == "configures library successfully" ||
309-
test.name == "configures library with error message in response" {
310-
if err := os.MkdirAll(filepath.Join(cfg.APISource, test.api), 0755); err != nil {
311-
t.Fatal(err)
312-
}
313-
314-
data := []byte("type: google.api.Service")
315-
if err := os.WriteFile(filepath.Join(cfg.APISource, test.api, "example_service_v2.yaml"), data, 0755); err != nil {
316-
t.Fatal(err)
317-
}
330+
// Create a service config
331+
if err := os.MkdirAll(filepath.Join(cfg.APISource, test.api), 0755); err != nil {
332+
t.Fatal(err)
318333
}
319334

320-
if test.name == "configures library with no response" ||
321-
test.name == "configure command failed" {
322-
if err := os.MkdirAll(filepath.Join(cfg.APISource, test.api), 0755); err != nil {
323-
t.Fatal(err)
324-
}
335+
data := []byte("type: google.api.Service")
336+
if err := os.WriteFile(filepath.Join(cfg.APISource, test.api, "example_service_v2.yaml"), data, 0755); err != nil {
337+
t.Fatal(err)
338+
}
325339

326-
data := []byte("type: google.api.Service")
327-
if err := os.WriteFile(filepath.Join(cfg.APISource, test.api, "example_service_v2.yaml"), data, 0755); err != nil {
340+
if test.name == "configures library with non-existent api source" {
341+
// This test verifies the scenario of no service config is found
342+
// in api path.
343+
if err := os.RemoveAll(filepath.Join(cfg.APISource)); err != nil {
328344
t.Fatal(err)
329345
}
330-
331-
// Do not create response file
332346
}
333347

334348
_, err := r.runConfigureCommand(context.Background())
@@ -338,6 +352,10 @@ func TestRunConfigureCommand(t *testing.T) {
338352
t.Errorf("runConfigureCommand() should return error")
339353
}
340354

355+
if !strings.Contains(err.Error(), test.wantErrMsg) {
356+
t.Errorf("runConfigureCommand() err = %v, want error containing %q", err, test.wantErrMsg)
357+
}
358+
341359
return
342360
}
343361

@@ -501,52 +519,6 @@ func TestNewGenerateRunner(t *testing.T) {
501519
}
502520
}
503521

504-
// newTestGitRepo creates a new git repository in a temporary directory.
505-
func newTestGitRepo(t *testing.T) gitrepo.Repository {
506-
return newTestGitRepoWithState(t, true)
507-
}
508-
509-
// newTestGitRepo creates a new git repository in a temporary directory.
510-
func newTestGitRepoWithState(t *testing.T, writeState bool) gitrepo.Repository {
511-
t.Helper()
512-
dir := t.TempDir()
513-
remoteURL := "https://github.com/googleapis/librarian.git"
514-
runGit(t, dir, "init")
515-
runGit(t, dir, "config", "user.email", "[email protected]")
516-
runGit(t, dir, "config", "user.name", "Test User")
517-
if err := os.WriteFile(filepath.Join(dir, "README.md"), []byte("test"), 0644); err != nil {
518-
t.Fatalf("os.WriteFile: %v", err)
519-
}
520-
if writeState {
521-
// Create an empty state.yaml file
522-
stateDir := filepath.Join(dir, config.LibrarianDir)
523-
if err := os.MkdirAll(stateDir, 0755); err != nil {
524-
t.Fatalf("os.MkdirAll: %v", err)
525-
}
526-
stateFile := filepath.Join(stateDir, "state.yaml")
527-
if err := os.WriteFile(stateFile, []byte(""), 0644); err != nil {
528-
t.Fatalf("os.WriteFile: %v", err)
529-
}
530-
}
531-
runGit(t, dir, "add", ".")
532-
runGit(t, dir, "commit", "-m", "initial commit")
533-
runGit(t, dir, "remote", "add", "origin", remoteURL)
534-
repo, err := gitrepo.NewRepository(&gitrepo.RepositoryOptions{Dir: dir})
535-
if err != nil {
536-
t.Fatalf("gitrepo.Open(%q) = %v", dir, err)
537-
}
538-
return repo
539-
}
540-
541-
func runGit(t *testing.T, dir string, args ...string) {
542-
t.Helper()
543-
cmd := exec.Command("git", args...)
544-
cmd.Dir = dir
545-
if err := cmd.Run(); err != nil {
546-
t.Fatalf("git %v: %v", args, err)
547-
}
548-
}
549-
550522
func TestGenerateRun(t *testing.T) {
551523
t.Parallel()
552524
for _, test := range []struct {
@@ -967,28 +939,15 @@ func TestGenerateScenarios(t *testing.T) {
967939
workRoot: t.TempDir(),
968940
}
969941

942+
// Create a service config in api path.
970943
if err := os.MkdirAll(filepath.Join(cfg.APISource, test.api), 0755); err != nil {
971944
t.Fatal(err)
972945
}
973-
974946
data := []byte("type: google.api.Service")
975947
if err := os.WriteFile(filepath.Join(cfg.APISource, test.api, "example_service_v2.yaml"), data, 0755); err != nil {
976948
t.Fatal(err)
977949
}
978950

979-
// Write a configure-response.json because it is required by configure
980-
// command.
981-
if err := os.MkdirAll(filepath.Join(r.repo.GetDir(), config.LibrarianDir), 0755); err != nil {
982-
t.Fatal(err)
983-
}
984-
985-
libraryStr := fmt.Sprintf(`{
986-
"ID": "%s"
987-
}`, test.library)
988-
if err := os.WriteFile(filepath.Join(r.repo.GetDir(), config.LibrarianDir, config.ConfigureResponse), []byte(libraryStr), 0755); err != nil {
989-
t.Fatal(err)
990-
}
991-
992951
err := r.run(context.Background())
993952
if test.wantErr {
994953
if err == nil {
@@ -1662,3 +1621,50 @@ func TestCleanAndCopyLibrary(t *testing.T) {
16621621
})
16631622
}
16641623
}
1624+
1625+
// newTestGitRepo creates a new git repository in a temporary directory.
1626+
func newTestGitRepo(t *testing.T) gitrepo.Repository {
1627+
t.Helper()
1628+
return newTestGitRepoWithState(t, true)
1629+
}
1630+
1631+
// newTestGitRepo creates a new git repository in a temporary directory.
1632+
func newTestGitRepoWithState(t *testing.T, writeState bool) gitrepo.Repository {
1633+
t.Helper()
1634+
dir := t.TempDir()
1635+
remoteURL := "https://github.com/googleapis/librarian.git"
1636+
runGit(t, dir, "init")
1637+
runGit(t, dir, "config", "user.email", "[email protected]")
1638+
runGit(t, dir, "config", "user.name", "Test User")
1639+
if err := os.WriteFile(filepath.Join(dir, "README.md"), []byte("test"), 0644); err != nil {
1640+
t.Fatalf("os.WriteFile: %v", err)
1641+
}
1642+
if writeState {
1643+
// Create an empty state.yaml file
1644+
stateDir := filepath.Join(dir, config.LibrarianDir)
1645+
if err := os.MkdirAll(stateDir, 0755); err != nil {
1646+
t.Fatalf("os.MkdirAll: %v", err)
1647+
}
1648+
stateFile := filepath.Join(stateDir, "state.yaml")
1649+
if err := os.WriteFile(stateFile, []byte(""), 0644); err != nil {
1650+
t.Fatalf("os.WriteFile: %v", err)
1651+
}
1652+
}
1653+
runGit(t, dir, "add", ".")
1654+
runGit(t, dir, "commit", "-m", "initial commit")
1655+
runGit(t, dir, "remote", "add", "origin", remoteURL)
1656+
repo, err := gitrepo.NewRepository(&gitrepo.RepositoryOptions{Dir: dir})
1657+
if err != nil {
1658+
t.Fatalf("gitrepo.Open(%q) = %v", dir, err)
1659+
}
1660+
return repo
1661+
}
1662+
1663+
func runGit(t *testing.T, dir string, args ...string) {
1664+
t.Helper()
1665+
cmd := exec.Command("git", args...)
1666+
cmd.Dir = dir
1667+
if err := cmd.Run(); err != nil {
1668+
t.Fatalf("git %v: %v", args, err)
1669+
}
1670+
}

internal/librarian/mocks_test.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"fmt"
2020
"os"
2121
"path/filepath"
22+
"strings"
2223

2324
"github.com/go-git/go-git/v5"
2425
"github.com/googleapis/librarian/internal/config"
@@ -69,6 +70,7 @@ type mockContainerClient struct {
6970
requestLibraryID string
7071
noBuildResponse bool
7172
noConfigureResponse bool
73+
noInitVersion bool
7274
wantErrorMsg bool
7375
}
7476

@@ -132,19 +134,16 @@ func (m *mockContainerClient) Configure(ctx context.Context, request *docker.Con
132134
return "", err
133135
}
134136

135-
libraryStr := ""
137+
var libraryBuilder strings.Builder
138+
libraryBuilder.WriteString(fmt.Sprintf("{\"id\":\"%s\"", request.State.Libraries[0].ID))
139+
if !m.noInitVersion {
140+
libraryBuilder.WriteString(",\"version\": \"0.1.0\"")
141+
}
136142
if m.wantErrorMsg {
137-
libraryStr = fmt.Sprintf(`{
138-
"ID": "%s",
139-
"error": "simulated error message"
140-
}`, request.State.Libraries[0].ID)
141-
} else {
142-
libraryStr = fmt.Sprintf(`{
143-
"ID": "%s"
144-
}`, request.State.Libraries[0].ID)
143+
libraryBuilder.WriteString(",\"error\": \"simulated error message\"")
145144
}
146-
147-
if err := os.WriteFile(filepath.Join(request.RepoDir, config.LibrarianDir, config.ConfigureResponse), []byte(libraryStr), 0755); err != nil {
145+
libraryBuilder.WriteString("}")
146+
if err := os.WriteFile(filepath.Join(request.RepoDir, config.LibrarianDir, config.ConfigureResponse), []byte(libraryBuilder.String()), 0755); err != nil {
148147
return "", err
149148
}
150149
return "", m.configureErr

internal/librarian/state.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func populateServiceConfigIfEmpty(state *config.LibrarianState, contentLoader fu
103103
func findServiceConfigIn(contentLoader func(file string) ([]byte, error), path string) (string, error) {
104104
entries, err := os.ReadDir(path)
105105
if err != nil {
106-
return "", err
106+
return "", fmt.Errorf("failed to read dir %q: %w", path, err)
107107
}
108108

109109
for _, entry := range entries {
@@ -135,7 +135,7 @@ func saveLibrarianState(repoDir string, state *config.LibrarianState) error {
135135
return os.WriteFile(path, bytes, 0644)
136136
}
137137

138-
// readLibraryState reads the library state from configure-response.json.
138+
// readLibraryState reads the library state from a container response.
139139
//
140140
// The response file is removed afterwards.
141141
func readLibraryState(contentLoader func(data []byte, state *config.LibraryState) error, jsonFilePath string) (*config.LibraryState, error) {

0 commit comments

Comments
 (0)