diff --git a/pkg/blueprint/blueprint_handler.go b/pkg/blueprint/blueprint_handler.go index 2f93e5601..55edd732f 100644 --- a/pkg/blueprint/blueprint_handler.go +++ b/pkg/blueprint/blueprint_handler.go @@ -146,6 +146,10 @@ func (b *BaseBlueprintHandler) LoadConfig() error { return err } + if err := b.setRepositoryDefaults(); err != nil { + return fmt.Errorf("error setting repository defaults: %w", err) + } + b.configLoaded = true return nil } @@ -1831,7 +1835,9 @@ func (b *BaseBlueprintHandler) setRepositoryDefaults() error { return nil } - if b.configHandler.GetBool("dev") { + devMode := b.configHandler.GetBool("dev") + + if devMode { url := b.getDevelopmentRepositoryURL() if url != "" { b.blueprint.Repository.Url = url @@ -1841,17 +1847,28 @@ func (b *BaseBlueprintHandler) setRepositoryDefaults() error { gitURL, err := b.shell.ExecSilent("git", "config", "--get", "remote.origin.url") if err == nil && gitURL != "" { - b.blueprint.Repository.Url = strings.TrimSpace(gitURL) + b.blueprint.Repository.Url = b.normalizeGitURL(strings.TrimSpace(gitURL)) return nil } return nil } +// normalizeGitURL normalizes git repository URLs by prepending https:// when needed. +// Preserves SSH URLs (git@...), http://, and https:// URLs as-is. +func (b *BaseBlueprintHandler) normalizeGitURL(url string) string { + if strings.HasPrefix(url, "git@") || + strings.HasPrefix(url, "http://") || + strings.HasPrefix(url, "https://") { + return url + } + return "https://" + url +} + // getDevelopmentRepositoryURL generates a development repository URL from configuration. // Returns URL in format: http://git./git/ func (b *BaseBlueprintHandler) getDevelopmentRepositoryURL() string { - domain := b.configHandler.GetString("dns.domain") + domain := b.configHandler.GetString("dns.domain", "test") if domain == "" { return "" } diff --git a/pkg/blueprint/blueprint_handler_private_test.go b/pkg/blueprint/blueprint_handler_private_test.go index 40e4cb74c..d17165be2 100644 --- a/pkg/blueprint/blueprint_handler_private_test.go +++ b/pkg/blueprint/blueprint_handler_private_test.go @@ -3511,6 +3511,33 @@ func TestBaseBlueprintHandler_setRepositoryDefaults(t *testing.T) { } }) + t.Run("PreservesSSHGitRemoteOrigin", func(t *testing.T) { + handler := setup(t) + + mockConfigHandler := handler.configHandler.(*config.MockConfigHandler) + mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { + return false + } + + mockShell := handler.shell.(*shell.MockShell) + mockShell.ExecSilentFunc = func(command string, args ...string) (string, error) { + if command == "git" && len(args) == 3 && args[0] == "config" && args[2] == "remote.origin.url" { + return "git@github.com:windsorcli/core.git\n", nil + } + return "", fmt.Errorf("command not found") + } + + err := handler.setRepositoryDefaults() + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + expectedURL := "git@github.com:windsorcli/core.git" + if handler.blueprint.Repository.Url != expectedURL { + t.Errorf("Expected URL to be %s, got %s", expectedURL, handler.blueprint.Repository.Url) + } + }) + t.Run("HandlesGitRemoteOriginError", func(t *testing.T) { handler := setup(t) @@ -3589,6 +3616,64 @@ func TestBaseBlueprintHandler_setRepositoryDefaults(t *testing.T) { t.Errorf("Expected URL to be %s, got %s", expectedURL, handler.blueprint.Repository.Url) } }) + +} + +func TestBaseBlueprintHandler_normalizeGitURL(t *testing.T) { + setup := func(t *testing.T) *BaseBlueprintHandler { + t.Helper() + mocks := setupMocks(t) + handler := NewBlueprintHandler(mocks.Injector) + return handler + } + + t.Run("PreservesSSHURL", func(t *testing.T) { + handler := setup(t) + + input := "git@github.com:windsorcli/core.git" + expected := "git@github.com:windsorcli/core.git" + result := handler.normalizeGitURL(input) + + if result != expected { + t.Errorf("Expected %s, got %s", expected, result) + } + }) + + t.Run("PreservesHTTPSURL", func(t *testing.T) { + handler := setup(t) + + input := "https://github.com/windsorcli/core.git" + expected := "https://github.com/windsorcli/core.git" + result := handler.normalizeGitURL(input) + + if result != expected { + t.Errorf("Expected %s, got %s", expected, result) + } + }) + + t.Run("PreservesHTTPURL", func(t *testing.T) { + handler := setup(t) + + input := "http://git.test/git/core" + expected := "http://git.test/git/core" + result := handler.normalizeGitURL(input) + + if result != expected { + t.Errorf("Expected %s, got %s", expected, result) + } + }) + + t.Run("PrependsHTTPSToPlainURL", func(t *testing.T) { + handler := setup(t) + + input := "github.com/windsorcli/core.git" + expected := "https://github.com/windsorcli/core.git" + result := handler.normalizeGitURL(input) + + if result != expected { + t.Errorf("Expected %s, got %s", expected, result) + } + }) } func TestBaseBlueprintHandler_getDevelopmentRepositoryURL(t *testing.T) { @@ -3630,11 +3715,14 @@ func TestBaseBlueprintHandler_getDevelopmentRepositoryURL(t *testing.T) { } }) - t.Run("ReturnsEmptyWhenDomainNotSet", func(t *testing.T) { + t.Run("UsesDefaultDomainWhenNotSet", func(t *testing.T) { handler := setup(t) mockConfigHandler := handler.configHandler.(*config.MockConfigHandler) mockConfigHandler.GetStringFunc = func(key string, defaultValue ...string) string { + if key == "dns.domain" && len(defaultValue) > 0 { + return defaultValue[0] + } return "" } @@ -3643,10 +3731,15 @@ func TestBaseBlueprintHandler_getDevelopmentRepositoryURL(t *testing.T) { return "/home/user/projects/my-project", nil } + handler.shims.FilepathBase = func(path string) string { + return "my-project" + } + url := handler.getDevelopmentRepositoryURL() - if url != "" { - t.Errorf("Expected empty URL when domain not set, got %s", url) + expectedURL := "http://git.test/git/my-project" + if url != expectedURL { + t.Errorf("Expected URL to be %s, got %s", expectedURL, url) } }) diff --git a/pkg/blueprint/blueprint_handler_public_test.go b/pkg/blueprint/blueprint_handler_public_test.go index 9784d878f..e4674d133 100644 --- a/pkg/blueprint/blueprint_handler_public_test.go +++ b/pkg/blueprint/blueprint_handler_public_test.go @@ -913,6 +913,81 @@ func TestBlueprintHandler_LoadConfig(t *testing.T) { t.Errorf("expected normalized path, got %q", ks[0].Path) } }) + + t.Run("SetsRepositoryDefaultsInDevMode", func(t *testing.T) { + handler, mocks := setup(t) + + mockConfigHandler := mocks.ConfigHandler.(*config.MockConfigHandler) + mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { + if key == "dev" { + return true + } + return false + } + mockConfigHandler.GetStringFunc = func(key string, defaultValue ...string) string { + if key == "dns.domain" && len(defaultValue) > 0 { + return defaultValue[0] + } + return "" + } + mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { + if key == "dev" { + return true + } + return false + } + mockConfigHandler.GetConfigRootFunc = func() (string, error) { + return "/tmp/test-config", nil + } + + mocks.Shell.GetProjectRootFunc = func() (string, error) { + return "/Users/test/project/cli", nil + } + + handler.shims.FilepathBase = func(path string) string { + if path == "/Users/test/project/cli" { + return "cli" + } + return "" + } + + handler.shims.Stat = func(name string) (os.FileInfo, error) { + if strings.HasSuffix(name, ".yaml") { + return nil, nil + } + return nil, os.ErrNotExist + } + + blueprintWithoutURL := `kind: Blueprint +apiVersion: v1alpha1 +metadata: + name: test-blueprint + description: A test blueprint +repository: + ref: + branch: main +sources: [] +terraform: [] +kustomize: []` + + handler.shims.ReadFile = func(name string) ([]byte, error) { + if strings.HasSuffix(name, ".yaml") { + return []byte(blueprintWithoutURL), nil + } + return nil, os.ErrNotExist + } + + err := handler.LoadConfig() + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + expectedURL := "http://git.test/git/cli" + if handler.blueprint.Repository.Url != expectedURL { + t.Errorf("Expected repository URL to be %s, got %s", expectedURL, handler.blueprint.Repository.Url) + } + }) } func TestBlueprintHandler_Install(t *testing.T) {