diff --git a/artifactory/commands/setup/setup.go b/artifactory/commands/setup/setup.go index 85651aa6..04d6ab62 100644 --- a/artifactory/commands/setup/setup.go +++ b/artifactory/commands/setup/setup.go @@ -143,12 +143,36 @@ func (sc *SetupCommand) SetProjectKey(projectKey string) *SetupCommand { return sc } +// validateRepoExistsFn is overridable so tests can stub out the network call. +var validateRepoExistsFn = validateRepoExists + +// validateRepoExists checks that the repository explicitly requested via --repo exists in Artifactory, +// so that a bad repo name fails fast with a clear error instead of surfacing later as a confusing +// package-manager-specific failure. +func validateRepoExists(sc *SetupCommand) error { + serviceDetails, err := sc.serverDetails.CreateArtAuthConfig() + if err != nil { + return err + } + return utils.ValidateRepoExists(sc.repoName, serviceDetails) +} + // Run executes the configuration method corresponding to the package manager specified for the command. func (sc *SetupCommand) Run() (err error) { if !IsSupportedPackageManager(sc.packageManager) { return errorutils.CheckErrorf("unsupported package manager: %s", sc.packageManager) } + // Validate the explicitly provided repository up front. This runs inside Run() (rather than in the + // CLI layer before Run() is invoked) so that every return path from here on is covered by the single + // usage-report call site in commands.Exec/ExecWithPackageManager - no separate reporting is needed + // for this failure. + if sc.repoName != "" { + if err = validateRepoExistsFn(sc); err != nil { + return err + } + } + // If the repository name is not provided, and the package manager is not Docker or Podman, prompt the user to select a repository. // Docker and Podman do not require a repository name as they authenticate directly with the platform and require the repository name as part of the image name. if sc.repoName == "" && sc.packageManager != project.Docker && sc.packageManager != project.Podman { diff --git a/artifactory/commands/setup/setup_test.go b/artifactory/commands/setup/setup_test.go index c02ec8d2..1edcb004 100644 --- a/artifactory/commands/setup/setup_test.go +++ b/artifactory/commands/setup/setup_test.go @@ -19,6 +19,7 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" "github.com/jfrog/jfrog-client-go/auth" + "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -29,6 +30,18 @@ const ( goProxyEnv = "GOPROXY" ) +// TestMain stubs out validateRepoExistsFn for every test in this package, since +// createTestSetupCommand points serverDetails at a fake host (acme.jfrog.io) +// that can't answer a real repo-existence check. Tests that specifically want +// to exercise validateRepoExists restore the real function for their own scope. +func TestMain(m *testing.M) { + realValidateRepoExistsFn := validateRepoExistsFn + validateRepoExistsFn = func(*SetupCommand) error { return nil } + code := m.Run() + validateRepoExistsFn = realValidateRepoExistsFn + os.Exit(code) +} + // testCredential returns a fake JWT-like string for testing. NOT a real credential. func testCredential() string { // Construct fake JWT parts separately to avoid secret detection @@ -67,6 +80,21 @@ func createTestSetupCommand(packageManager project.ProjectType) *SetupCommand { return cmd } +// TestSetupCommand_RepoValidationFailure verifies that Run() returns the repo-validation +// error immediately, before attempting any package-manager-specific configuration. +func TestSetupCommand_RepoValidationFailure(t *testing.T) { + realValidateRepoExistsFn := validateRepoExistsFn + defer func() { validateRepoExistsFn = realValidateRepoExistsFn }() + + expectedErr := errorutils.CheckErrorf("the repository 'test-repo' does not exist") + validateRepoExistsFn = func(*SetupCommand) error { return expectedErr } + + cmd := createTestSetupCommand(project.Npm) + err := cmd.Run() + require.Error(t, err) + assert.Equal(t, expectedErr, err) +} + func TestSetupCommand_NotSupported(t *testing.T) { notSupportedLoginCmd := createTestSetupCommand(project.Cocoapods) err := notSupportedLoginCmd.Run()