From ad929e3919d6630d9d97c902868caf815cc39117 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Sun, 12 Oct 2025 12:02:37 -0400 Subject: [PATCH] feature(blueprint): Set default repository URL Sets a default repository URL. For local development, this is something like `git.test`. Otherwise, it attempts to retreive the current remote origin URL from the git repository. --- pkg/blueprint/blueprint_handler.go | 49 +++ .../blueprint_handler_private_test.go | 307 ++++++++++++++++++ 2 files changed, 356 insertions(+) diff --git a/pkg/blueprint/blueprint_handler.go b/pkg/blueprint/blueprint_handler.go index 5c0c5dc79..9a0a29a29 100644 --- a/pkg/blueprint/blueprint_handler.go +++ b/pkg/blueprint/blueprint_handler.go @@ -198,6 +198,10 @@ func (b *BaseBlueprintHandler) Write(overwrite ...bool) error { return fmt.Errorf("error creating directory: %w", err) } + if err := b.setRepositoryDefaults(); err != nil { + return fmt.Errorf("error setting repository defaults: %w", err) + } + cleanedBlueprint := b.blueprint.DeepCopy() for i := range cleanedBlueprint.TerraformComponents { cleanedBlueprint.TerraformComponents[i].Values = map[string]any{} @@ -1808,3 +1812,48 @@ func (b *BaseBlueprintHandler) deepMergeMaps(base, overlay map[string]any) map[s } return result } + +// setRepositoryDefaults sets the blueprint repository URL if not already specified. +// Uses development URL if dev flag is enabled, otherwise falls back to git remote origin URL. +func (b *BaseBlueprintHandler) setRepositoryDefaults() error { + if b.blueprint.Repository.Url != "" { + return nil + } + + if b.configHandler.GetBool("dev") { + url := b.getDevelopmentRepositoryURL() + if url != "" { + b.blueprint.Repository.Url = url + return nil + } + } + + gitURL, err := b.shell.ExecSilent("git", "config", "--get", "remote.origin.url") + if err == nil && gitURL != "" { + b.blueprint.Repository.Url = strings.TrimSpace(gitURL) + return nil + } + + return nil +} + +// 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") + if domain == "" { + return "" + } + + projectRoot, err := b.shell.GetProjectRoot() + if err != nil { + return "" + } + + folder := b.shims.FilepathBase(projectRoot) + if folder == "" { + return "" + } + + return fmt.Sprintf("http://git.%s/git/%s", domain, folder) +} diff --git a/pkg/blueprint/blueprint_handler_private_test.go b/pkg/blueprint/blueprint_handler_private_test.go index 5ac5216bd..a434d790b 100644 --- a/pkg/blueprint/blueprint_handler_private_test.go +++ b/pkg/blueprint/blueprint_handler_private_test.go @@ -3633,3 +3633,310 @@ terraform: } }) } + +func TestBaseBlueprintHandler_setRepositoryDefaults(t *testing.T) { + setup := func(t *testing.T) *BaseBlueprintHandler { + t.Helper() + mocks := setupMocks(t) + handler := NewBlueprintHandler(mocks.Injector) + handler.shims = mocks.Shims + handler.configHandler = mocks.ConfigHandler + handler.shell = mocks.Shell + return handler + } + + t.Run("PreservesExistingRepositoryURL", func(t *testing.T) { + handler := setup(t) + handler.blueprint.Repository.Url = "https://github.com/existing/repo" + + err := handler.setRepositoryDefaults() + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if handler.blueprint.Repository.Url != "https://github.com/existing/repo" { + t.Errorf("Expected URL to remain unchanged, got %s", handler.blueprint.Repository.Url) + } + }) + + t.Run("UsesDevelopmentURLWhenDevFlagEnabled", func(t *testing.T) { + handler := setup(t) + + mockConfigHandler := handler.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" { + return "example.com" + } + return "" + } + + mockShell := handler.shell.(*shell.MockShell) + mockShell.GetProjectRootFunc = func() (string, error) { + return "/path/to/my-project", nil + } + + handler.shims.FilepathBase = func(path string) string { + return "my-project" + } + + err := handler.setRepositoryDefaults() + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + expectedURL := "http://git.example.com/git/my-project" + if handler.blueprint.Repository.Url != expectedURL { + t.Errorf("Expected URL to be %s, got %s", expectedURL, handler.blueprint.Repository.Url) + } + }) + + t.Run("FallsBackToGitRemoteOrigin", 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 "https://github.com/user/repo.git\n", nil + } + return "", fmt.Errorf("command not found") + } + + err := handler.setRepositoryDefaults() + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + expectedURL := "https://github.com/user/repo.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) + + 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) { + return "", fmt.Errorf("not a git repository") + } + + err := handler.setRepositoryDefaults() + + if err != nil { + t.Fatalf("Expected no error even when git fails, got %v", err) + } + if handler.blueprint.Repository.Url != "" { + t.Errorf("Expected URL to remain empty when git fails, got %s", handler.blueprint.Repository.Url) + } + }) + + t.Run("HandlesEmptyGitRemoteOriginOutput", 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) { + return "", nil + } + + err := handler.setRepositoryDefaults() + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if handler.blueprint.Repository.Url != "" { + t.Errorf("Expected URL to remain empty, got %s", handler.blueprint.Repository.Url) + } + }) + + t.Run("DevModeFallsBackToGitWhenDevelopmentURLFails", func(t *testing.T) { + handler := setup(t) + + mockConfigHandler := handler.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 { + return "" + } + + mockShell := handler.shell.(*shell.MockShell) + mockShell.ExecSilentFunc = func(command string, args ...string) (string, error) { + if command == "git" { + return "https://github.com/fallback/repo.git", nil + } + return "", fmt.Errorf("command not found") + } + + err := handler.setRepositoryDefaults() + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + expectedURL := "https://github.com/fallback/repo.git" + if handler.blueprint.Repository.Url != expectedURL { + t.Errorf("Expected URL to be %s, got %s", expectedURL, handler.blueprint.Repository.Url) + } + }) +} + +func TestBaseBlueprintHandler_getDevelopmentRepositoryURL(t *testing.T) { + setup := func(t *testing.T) *BaseBlueprintHandler { + t.Helper() + mocks := setupMocks(t) + handler := NewBlueprintHandler(mocks.Injector) + handler.shims = mocks.Shims + handler.configHandler = mocks.ConfigHandler + handler.shell = mocks.Shell + return handler + } + + t.Run("GeneratesCorrectDevelopmentURL", func(t *testing.T) { + handler := setup(t) + + mockConfigHandler := handler.configHandler.(*config.MockConfigHandler) + mockConfigHandler.GetStringFunc = func(key string, defaultValue ...string) string { + if key == "dns.domain" { + return "dev.example.com" + } + return "" + } + + mockShell := handler.shell.(*shell.MockShell) + mockShell.GetProjectRootFunc = func() (string, error) { + return "/home/user/projects/my-awesome-project", nil + } + + handler.shims.FilepathBase = func(path string) string { + return "my-awesome-project" + } + + url := handler.getDevelopmentRepositoryURL() + + expectedURL := "http://git.dev.example.com/git/my-awesome-project" + if url != expectedURL { + t.Errorf("Expected URL to be %s, got %s", expectedURL, url) + } + }) + + t.Run("ReturnsEmptyWhenDomainNotSet", func(t *testing.T) { + handler := setup(t) + + mockConfigHandler := handler.configHandler.(*config.MockConfigHandler) + mockConfigHandler.GetStringFunc = func(key string, defaultValue ...string) string { + return "" + } + + mockShell := handler.shell.(*shell.MockShell) + mockShell.GetProjectRootFunc = func() (string, error) { + return "/home/user/projects/my-project", nil + } + + url := handler.getDevelopmentRepositoryURL() + + if url != "" { + t.Errorf("Expected empty URL when domain not set, got %s", url) + } + }) + + t.Run("ReturnsEmptyWhenProjectRootFails", func(t *testing.T) { + handler := setup(t) + + mockConfigHandler := handler.configHandler.(*config.MockConfigHandler) + mockConfigHandler.GetStringFunc = func(key string, defaultValue ...string) string { + if key == "dns.domain" { + return "example.com" + } + return "" + } + + mockShell := handler.shell.(*shell.MockShell) + mockShell.GetProjectRootFunc = func() (string, error) { + return "", fmt.Errorf("project root not found") + } + + url := handler.getDevelopmentRepositoryURL() + + if url != "" { + t.Errorf("Expected empty URL when project root fails, got %s", url) + } + }) + + t.Run("ReturnsEmptyWhenFolderNameEmpty", func(t *testing.T) { + handler := setup(t) + + mockConfigHandler := handler.configHandler.(*config.MockConfigHandler) + mockConfigHandler.GetStringFunc = func(key string, defaultValue ...string) string { + if key == "dns.domain" { + return "example.com" + } + return "" + } + + mockShell := handler.shell.(*shell.MockShell) + mockShell.GetProjectRootFunc = func() (string, error) { + return "/home/user/projects/", nil + } + + handler.shims.FilepathBase = func(path string) string { + return "" + } + + url := handler.getDevelopmentRepositoryURL() + + if url != "" { + t.Errorf("Expected empty URL when folder name is empty, got %s", url) + } + }) + + t.Run("HandlesComplexProjectPaths", func(t *testing.T) { + handler := setup(t) + + mockConfigHandler := handler.configHandler.(*config.MockConfigHandler) + mockConfigHandler.GetStringFunc = func(key string, defaultValue ...string) string { + if key == "dns.domain" { + return "staging.example.io" + } + return "" + } + + mockShell := handler.shell.(*shell.MockShell) + mockShell.GetProjectRootFunc = func() (string, error) { + return "/var/www/projects/nested/deep/project-with-dashes", nil + } + + handler.shims.FilepathBase = func(path string) string { + return "project-with-dashes" + } + + url := handler.getDevelopmentRepositoryURL() + + expectedURL := "http://git.staging.example.io/git/project-with-dashes" + if url != expectedURL { + t.Errorf("Expected URL to be %s, got %s", expectedURL, url) + } + }) +}