From 6e209ceeff642cda136c38c04130412c04b673f7 Mon Sep 17 00:00:00 2001 From: Zorian Motso Date: Mon, 10 Nov 2025 11:50:23 +0200 Subject: [PATCH] refactor: Project onboarding workflow (#231) - Refactored `put_project.go` to improve code maintainability - Extracted error handling into reusable `handleError` helper method - Consolidated git provider initialization throughout the code - Simplified push operation to use single call with multiple refspecs - Improved error messages with more context - Rewrote tests in `put_project_test.go` with better coverage - Split tests into separate files for third-party Git providers and Gerrit - Added comprehensive test cases for success and error scenarios - Reduced test complexity and improved maintainability - Enhanced GitServer API documentation - Added detailed description for `nameSshKeySecret` field - Documented required and optional secret keys for each Git provider - Move Git package from v2 to main pkg directory - Updated import paths across the codebase - Removed deprecated package files --- .mockery.yaml | 4 - api/v1/git_server_types.go | 10 + .../crd/bases/v2.edp.epam.com_gitservers.yaml | 10 + .../codebase/service/chain/checkout_branch.go | 2 +- .../service/chain/checkout_branch_test.go | 4 +- controllers/codebase/service/chain/common.go | 2 +- .../codebase/service/chain/common_test.go | 4 +- controllers/codebase/service/chain/factory.go | 2 +- .../service/chain/put_deploy_configs.go | 2 +- .../service/chain/put_deploy_configs_test.go | 4 +- .../service/chain/put_gitlab_ci_config.go | 2 +- .../chain/put_gitlab_ci_config_test.go | 4 +- .../codebase/service/chain/put_project.go | 112 +- .../service/chain/put_project_test.go | 2125 +++++++---------- .../codebasebranch/chain/check_reference.go | 2 +- .../chain/check_reference_test.go | 4 +- .../codebasebranch/chain/factory/factory.go | 2 +- .../put_branch_in_git/put_branch_in_git.go | 2 +- .../put_branch_in_git_test.go | 4 +- .../crds/v2.edp.epam.com_gitservers.yaml | 10 + docs/api.md | 9 +- pkg/git/{v2 => }/factory.go | 0 pkg/git/git.go | 896 +------ pkg/git/git_test.go | 577 ----- pkg/git/mocks/command_mock.go | 89 - pkg/git/mocks/git_mock.go | 578 ++--- pkg/git/{v2 => }/provider.go | 0 pkg/git/provider_test.go | 1985 +++++++++++++++ pkg/git/v2/git.go | 59 - pkg/git/v2/mocks/git_mock.go | 855 ------- pkg/git/v2/provider_test.go | 520 ---- sonar-project.properties | 2 +- 32 files changed, 3292 insertions(+), 4589 deletions(-) rename pkg/git/{v2 => }/factory.go (100%) delete mode 100644 pkg/git/git_test.go delete mode 100644 pkg/git/mocks/command_mock.go rename pkg/git/{v2 => }/provider.go (100%) create mode 100644 pkg/git/provider_test.go delete mode 100644 pkg/git/v2/git.go delete mode 100644 pkg/git/v2/mocks/git_mock.go delete mode 100644 pkg/git/v2/provider_test.go diff --git a/.mockery.yaml b/.mockery.yaml index 97a6c1e7..fe00407b 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -21,10 +21,6 @@ packages: interfaces: Client: github.com/epam/edp-codebase-operator/v2/pkg/git: - interfaces: - Git: - Command: - github.com/epam/edp-codebase-operator/v2/pkg/git/v2: interfaces: Git: github.com/epam/edp-codebase-operator/v2/pkg/gitprovider: diff --git a/api/v1/git_server_types.go b/api/v1/git_server_types.go index b863fef5..ad6a159f 100644 --- a/api/v1/git_server_types.go +++ b/api/v1/git_server_types.go @@ -24,6 +24,16 @@ type GitServerSpec struct { SshPort int32 `json:"sshPort"` + // NameSshKeySecret is the name of the Kubernetes secret containing Git repository credentials. + // Required keys: + // - token: Git provider access token (required) + // Optional keys: + // - id_rsa: SSH private key for Git operations over SSH + // - secretString: Webhook secret for validating webhook requests + // - username: Git username to override the default GitUser + // For Gerrit provider, only id_rsa key is required and used. + // +kubebuilder:example:=my-git-credentials + // +required NameSshKeySecret string `json:"nameSshKeySecret"` // GitProvider is a git provider type. It can be gerrit, github or gitlab. Default value is gerrit. diff --git a/config/crd/bases/v2.edp.epam.com_gitservers.yaml b/config/crd/bases/v2.edp.epam.com_gitservers.yaml index 16bfe100..7fba3638 100644 --- a/config/crd/bases/v2.edp.epam.com_gitservers.yaml +++ b/config/crd/bases/v2.edp.epam.com_gitservers.yaml @@ -74,6 +74,16 @@ spec: format: int32 type: integer nameSshKeySecret: + description: |- + NameSshKeySecret is the name of the Kubernetes secret containing Git repository credentials. + Required keys: + - token: Git provider access token (required) + Optional keys: + - id_rsa: SSH private key for Git operations over SSH + - secretString: Webhook secret for validating webhook requests + - username: Git username to override the default GitUser + For Gerrit provider, only id_rsa key is required and used. + example: my-git-credentials type: string skipWebhookSSLVerification: description: SkipWebhookSSLVerification is a flag to skip webhook diff --git a/controllers/codebase/service/chain/checkout_branch.go b/controllers/codebase/service/chain/checkout_branch.go index 03fbd184..fb10219b 100644 --- a/controllers/codebase/service/chain/checkout_branch.go +++ b/controllers/codebase/service/chain/checkout_branch.go @@ -9,7 +9,7 @@ import ( codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1" codebaseutil "github.com/epam/edp-codebase-operator/v2/pkg/codebase" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" + gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git" ) func CheckoutBranch( diff --git a/controllers/codebase/service/chain/checkout_branch_test.go b/controllers/codebase/service/chain/checkout_branch_test.go index 6a158f88..b7099e06 100644 --- a/controllers/codebase/service/chain/checkout_branch_test.go +++ b/controllers/codebase/service/chain/checkout_branch_test.go @@ -14,8 +14,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" - gitServerMocks "github.com/epam/edp-codebase-operator/v2/pkg/git/v2/mocks" + gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git" + gitServerMocks "github.com/epam/edp-codebase-operator/v2/pkg/git/mocks" "github.com/epam/edp-codebase-operator/v2/pkg/util" ) diff --git a/controllers/codebase/service/chain/common.go b/controllers/codebase/service/chain/common.go index eb221fde..22d9d0cc 100644 --- a/controllers/codebase/service/chain/common.go +++ b/controllers/codebase/service/chain/common.go @@ -11,7 +11,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" + gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git" "github.com/epam/edp-codebase-operator/v2/pkg/util" ) diff --git a/controllers/codebase/service/chain/common_test.go b/controllers/codebase/service/chain/common_test.go index cc9a54f7..3585896c 100644 --- a/controllers/codebase/service/chain/common_test.go +++ b/controllers/codebase/service/chain/common_test.go @@ -16,8 +16,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" - gitMocks "github.com/epam/edp-codebase-operator/v2/pkg/git/v2/mocks" + gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git" + gitMocks "github.com/epam/edp-codebase-operator/v2/pkg/git/mocks" "github.com/epam/edp-codebase-operator/v2/pkg/util" ) diff --git a/controllers/codebase/service/chain/factory.go b/controllers/codebase/service/chain/factory.go index 06e5c8b0..ccad5236 100644 --- a/controllers/codebase/service/chain/factory.go +++ b/controllers/codebase/service/chain/factory.go @@ -9,7 +9,7 @@ import ( "github.com/epam/edp-codebase-operator/v2/controllers/codebase/service/chain/handler" "github.com/epam/edp-codebase-operator/v2/pkg/gerrit" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" + gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git" gitlabci "github.com/epam/edp-codebase-operator/v2/pkg/gitlab" "github.com/epam/edp-codebase-operator/v2/pkg/gitprovider" ) diff --git a/controllers/codebase/service/chain/put_deploy_configs.go b/controllers/codebase/service/chain/put_deploy_configs.go index dc5d3360..0d1a7f9c 100644 --- a/controllers/codebase/service/chain/put_deploy_configs.go +++ b/controllers/codebase/service/chain/put_deploy_configs.go @@ -9,7 +9,7 @@ import ( codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1" "github.com/epam/edp-codebase-operator/v2/controllers/codebase/service/template" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" + gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git" "github.com/epam/edp-codebase-operator/v2/pkg/util" ) diff --git a/controllers/codebase/service/chain/put_deploy_configs_test.go b/controllers/codebase/service/chain/put_deploy_configs_test.go index 8afebf9e..9cd8a042 100644 --- a/controllers/codebase/service/chain/put_deploy_configs_test.go +++ b/controllers/codebase/service/chain/put_deploy_configs_test.go @@ -13,8 +13,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" - gitServerMocks "github.com/epam/edp-codebase-operator/v2/pkg/git/v2/mocks" + gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git" + gitServerMocks "github.com/epam/edp-codebase-operator/v2/pkg/git/mocks" "github.com/epam/edp-codebase-operator/v2/pkg/platform" "github.com/epam/edp-codebase-operator/v2/pkg/util" ) diff --git a/controllers/codebase/service/chain/put_gitlab_ci_config.go b/controllers/codebase/service/chain/put_gitlab_ci_config.go index c3acb9ab..d5ce5d0b 100644 --- a/controllers/codebase/service/chain/put_gitlab_ci_config.go +++ b/controllers/codebase/service/chain/put_gitlab_ci_config.go @@ -10,7 +10,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" + gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git" gitlabci "github.com/epam/edp-codebase-operator/v2/pkg/gitlab" "github.com/epam/edp-codebase-operator/v2/pkg/util" ) diff --git a/controllers/codebase/service/chain/put_gitlab_ci_config_test.go b/controllers/codebase/service/chain/put_gitlab_ci_config_test.go index 30f87f18..b8d9c52c 100644 --- a/controllers/codebase/service/chain/put_gitlab_ci_config_test.go +++ b/controllers/codebase/service/chain/put_gitlab_ci_config_test.go @@ -18,8 +18,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" - gitmocks "github.com/epam/edp-codebase-operator/v2/pkg/git/v2/mocks" + gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git" + gitmocks "github.com/epam/edp-codebase-operator/v2/pkg/git/mocks" gitlabci "github.com/epam/edp-codebase-operator/v2/pkg/gitlab" "github.com/epam/edp-codebase-operator/v2/pkg/util" ) diff --git a/controllers/codebase/service/chain/put_project.go b/controllers/codebase/service/chain/put_project.go index 7eeb027a..5e4d6d67 100644 --- a/controllers/codebase/service/chain/put_project.go +++ b/controllers/codebase/service/chain/put_project.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "slices" "strconv" @@ -15,7 +16,7 @@ import ( codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1" codebaseutil "github.com/epam/edp-codebase-operator/v2/pkg/codebase" "github.com/epam/edp-codebase-operator/v2/pkg/gerrit" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" + gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git" "github.com/epam/edp-codebase-operator/v2/pkg/gitprovider" "github.com/epam/edp-codebase-operator/v2/pkg/util" ) @@ -25,6 +26,7 @@ type PutProject struct { gerritClient gerrit.Client gitApiProjectProvider func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) gitProviderFactory gitproviderv2.GitProviderFactory + gitProviderNoAuth gitproviderv2.Git } var ( @@ -35,14 +37,15 @@ var ( func NewPutProject( c client.Client, gerritProvider gerrit.Client, - gitProjectProvider func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error), + gitApiProjectProvider func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error), gitProviderFactory gitproviderv2.GitProviderFactory, ) *PutProject { return &PutProject{ k8sClient: c, gerritClient: gerritProvider, - gitApiProjectProvider: gitProjectProvider, + gitApiProjectProvider: gitApiProjectProvider, gitProviderFactory: gitProviderFactory, + gitProviderNoAuth: gitProviderFactory(gitproviderv2.Config{}), } } @@ -59,37 +62,26 @@ func (h *PutProject) ServeRequest(ctx context.Context, codebase *codebaseApi.Cod err := setIntermediateSuccessFields(ctx, h.k8sClient, codebase, codebaseApi.RepositoryProvisioning) if err != nil { - return fmt.Errorf("failed to update Codebase %v status: %w", codebase.Name, err) + return fmt.Errorf("failed to update codebase %s status: %w", codebase.Name, err) } repoContext, err := GetGitRepositoryContext(ctx, h.k8sClient, codebase) if err != nil { - setFailedFields(codebase, codebaseApi.RepositoryProvisioning, err.Error()) - - return fmt.Errorf("failed to get git repository context: %w", err) - } - - if err = util.CreateDirectory(repoContext.WorkDir); err != nil { - setFailedFields(codebase, codebaseApi.RepositoryProvisioning, err.Error()) - - return fmt.Errorf("failed to create dir %q: %w", repoContext.WorkDir, err) + return h.handleError(codebase, err, "failed to get git repository context") } err = h.initialProjectProvisioning(ctx, codebase, repoContext) if err != nil { - setFailedFields(codebase, codebaseApi.RepositoryProvisioning, err.Error()) - return fmt.Errorf("failed to perform initial provisioning of codebase %v: %w", codebase.Name, err) + return h.handleError(codebase, err, fmt.Sprintf("failed to perform initial provisioning of codebase %s", codebase.Name)) } if err = h.checkoutBranch(ctrl.LoggerInto(ctx, log), codebase, repoContext); err != nil { - setFailedFields(codebase, codebaseApi.RepositoryProvisioning, err.Error()) - return err + return h.handleError(codebase, err, "failed to checkout branch") } err = h.createProject(ctrl.LoggerInto(ctx, log), codebase, repoContext) if err != nil { - setFailedFields(codebase, codebaseApi.RepositoryProvisioning, err.Error()) - return fmt.Errorf("failed to create project: %w", err) + return h.handleError(codebase, err, "failed to create project") } if err = updateGitStatusWithPatch(ctx, h.k8sClient, codebase, codebaseApi.RepositoryProvisioning, util.ProjectPushedStatus); err != nil { @@ -117,17 +109,21 @@ func (*PutProject) skip(ctx context.Context, codebase *codebaseApi.Codebase) boo return false } +// handleError sets failed fields on codebase and returns formatted error. +func (*PutProject) handleError(codebase *codebaseApi.Codebase, err error, message string) error { + setFailedFields(codebase, codebaseApi.RepositoryProvisioning, err.Error()) + return fmt.Errorf("%s: %w", message, err) +} + func (h *PutProject) createProject( ctx context.Context, codebase *codebaseApi.Codebase, repoContext *GitRepositoryContext, ) error { - gitProvider := h.gitProviderFactory(gitproviderv2.NewConfigFromGitServerAndSecret(repoContext.GitServer, repoContext.GitServerSecret)) - if repoContext.GitServer.Spec.GitProvider == codebaseApi.GitProviderGerrit { err := h.createGerritProject(ctx, repoContext.GitServer, repoContext.PrivateSSHKey, codebase.Spec.GetProjectID()) if err != nil { - return fmt.Errorf("failed to create project in Gerrit for codebase %v: %w", codebase.Name, err) + return fmt.Errorf("failed to create project in Gerrit for codebase %s: %w", codebase.Name, err) } } else { if err := h.createGitThirdPartyProject(ctx, repoContext.GitServer, repoContext.Token, codebase); err != nil { @@ -135,7 +131,7 @@ func (h *PutProject) createProject( } } - err := h.pushProject(ctx, gitProvider, codebase.Spec.GetProjectID(), repoContext) + err := h.pushProject(ctx, codebase.Spec.GetProjectID(), repoContext) if err != nil { return err } @@ -156,7 +152,7 @@ func (h *PutProject) replaceDefaultBranch(ctx context.Context, g gitproviderv2.G log.Info("Removing default branch") if err := g.RemoveBranch(ctx, directory, defaultBranchName); err != nil { - return fmt.Errorf("failed to remove master branch: %w", err) + return fmt.Errorf("failed to remove default branch %s: %w", defaultBranchName, err) } log.Info("Creating new branch") @@ -170,13 +166,15 @@ func (h *PutProject) replaceDefaultBranch(ctx context.Context, g gitproviderv2.G return nil } -func (h *PutProject) pushProject(ctx context.Context, g gitproviderv2.Git, projectName string, repoContext *GitRepositoryContext) error { +func (h *PutProject) pushProject(ctx context.Context, projectName string, repoContext *GitRepositoryContext) error { log := ctrl.LoggerFrom(ctx).WithValues("gitProvider", repoContext.GitServer.Spec.GitProvider) log.Info("Start pushing project") log.Info("Start adding remote link") - if err := g.AddRemoteLink( + gitProvider := h.gitProviderFactory(gitproviderv2.NewConfigFromGitServerAndSecret(repoContext.GitServer, repoContext.GitServerSecret)) + + if err := gitProvider.AddRemoteLink( ctx, repoContext.WorkDir, util.GetProjectGitUrl(repoContext.GitServer, repoContext.GitServerSecret, projectName), @@ -184,16 +182,10 @@ func (h *PutProject) pushProject(ctx context.Context, g gitproviderv2.Git, proje return fmt.Errorf("failed to add remote link: %w", err) } - log.Info("Start pushing changes into git") - - if err := g.Push(ctx, repoContext.WorkDir, gitproviderv2.RefSpecPushAllBranches); err != nil { - return fmt.Errorf("failed to push changes: %w", err) - } - - log.Info("Start pushing tags into git") + log.Info("Start pushing changes and tags into git") - if err := g.Push(ctx, repoContext.WorkDir, gitproviderv2.RefSpecPushAllTags); err != nil { - return fmt.Errorf("failed to push changes into git: %w", err) + if err := gitProvider.Push(ctx, repoContext.WorkDir, gitproviderv2.RefSpecPushAllBranches, gitproviderv2.RefSpecPushAllTags); err != nil { + return fmt.Errorf("failed to push changes and tags into git: %w", err) } log.Info("Project has been pushed successfully") @@ -212,7 +204,7 @@ func (h *PutProject) createGerritProject(ctx context.Context, gitServer *codebas } if projectExist { - log.Info("Skip creating project in Gerrit, project already exist") + log.Info("Skip creating project in Gerrit, project already exists") return nil } @@ -236,18 +228,17 @@ func (h *PutProject) checkoutBranch(ctx context.Context, codebase *codebaseApi.C gitprovider := h.gitProviderFactory(gitproviderv2.NewConfigFromGitServerAndSecret(repoContext.GitServer, repoContext.GitServerSecret)) - // TODO: branchToCopyInDefaultBranch is never used. Check if we can remove it. if codebase.Spec.BranchToCopyInDefaultBranch != "" && codebase.Spec.DefaultBranch != codebase.Spec.BranchToCopyInDefaultBranch { log.Info("Start checkout branch to copy") if err := CheckoutBranch(ctx, codebase.Spec.BranchToCopyInDefaultBranch, repoContext, codebase, h.k8sClient, h.gitProviderFactory); err != nil { - return fmt.Errorf("failed to checkout default branch %s: %w", codebase.Spec.DefaultBranch, err) + return fmt.Errorf("failed to checkout branch to copy %s: %w", codebase.Spec.BranchToCopyInDefaultBranch, err) } log.Info("Start replace default branch") if err := h.replaceDefaultBranch(ctx, gitprovider, repoContext.WorkDir, codebase.Spec.DefaultBranch, codebase.Spec.BranchToCopyInDefaultBranch); err != nil { - return fmt.Errorf("failed to replace master: %w", err) + return fmt.Errorf("failed to replace default branch %s with %s: %w", codebase.Spec.DefaultBranch, codebase.Spec.BranchToCopyInDefaultBranch, err) } return nil @@ -270,7 +261,6 @@ func (h *PutProject) createGitThirdPartyProject( gitProviderToken string, codebase *codebaseApi.Codebase, ) error { - projectName := codebase.Spec.GetProjectID() log := ctrl.LoggerFrom(ctx).WithValues("gitProvider", gitServer.Spec.GitProvider) log.Info("Start creating project in git provider") @@ -280,6 +270,8 @@ func (h *PutProject) createGitThirdPartyProject( return fmt.Errorf("failed to create git provider: %w", err) } + projectName := codebase.Spec.GetProjectID() + projectExists, err := gitProvider.ProjectExists( ctx, gitprovider.GetGitProviderAPIURL(gitServer), @@ -291,7 +283,7 @@ func (h *PutProject) createGitThirdPartyProject( } if projectExists { - log.Info("Skip creating project in git provider, project already exist") + log.Info("Skip creating project in git provider, project already exists") return nil } @@ -320,8 +312,7 @@ func (h *PutProject) setDefaultBranch( gitProviderToken, privateSSHKey string, ) error { - log := ctrl.LoggerFrom(ctx). - WithValues("gitProvider", gitServer.Spec.GitProvider) + log := ctrl.LoggerFrom(ctx).WithValues("gitProvider", gitServer.Spec.GitProvider) log.Info("Start setting default branch", "defaultBranch", codebase.Spec.DefaultBranch) @@ -391,7 +382,7 @@ func (h *PutProject) tryToCloneRepo( log.Info("Start cloning repository") - if util.DoesDirectoryExist(repoContext.WorkDir + "/.git") { + if util.DoesDirectoryExist(filepath.Join(repoContext.WorkDir, ".git")) { log.Info("Repository already exists") return nil @@ -419,18 +410,16 @@ func (h *PutProject) squashCommits(ctx context.Context, workDir string, strategy log.Info("Start squashing commits") - err := os.RemoveAll(workDir + "/.git") + err := os.RemoveAll(filepath.Join(workDir, ".git")) if err != nil { return fmt.Errorf("failed to remove .git folder: %w", err) } - gitProvider := h.gitProviderFactory(gitproviderv2.Config{}) - - if err := gitProvider.Init(ctx, workDir); err != nil { + if err := h.gitProviderNoAuth.Init(ctx, workDir); err != nil { return fmt.Errorf("failed to create git repository: %w", err) } - if err := gitProvider.Commit(ctx, workDir, "Initial commit"); err != nil { + if err := h.gitProviderNoAuth.Commit(ctx, workDir, "Initial commit"); err != nil { return fmt.Errorf("failed to commit all default content: %w", err) } @@ -440,6 +429,10 @@ func (h *PutProject) squashCommits(ctx context.Context, workDir string, strategy } func (h *PutProject) initialProjectProvisioning(ctx context.Context, codebase *codebaseApi.Codebase, repoContext *GitRepositoryContext) error { + if err := util.CreateDirectory(repoContext.WorkDir); err != nil { + return h.handleError(codebase, err, fmt.Sprintf("failed to create dir %q", repoContext.WorkDir)) + } + if codebase.Spec.EmptyProject { return h.emptyProjectProvisioning(ctx, repoContext) } @@ -452,15 +445,13 @@ func (h *PutProject) emptyProjectProvisioning(ctx context.Context, repoContext * log.Info("Initialing empty git repository") - gitProvider := h.gitProviderFactory(gitproviderv2.Config{}) - - if err := gitProvider.Init(ctx, repoContext.WorkDir); err != nil { + if err := h.gitProviderNoAuth.Init(ctx, repoContext.WorkDir); err != nil { return fmt.Errorf("failed to create empty git repository: %w", err) } log.Info("Making initial commit") - if err := gitProvider.Commit(ctx, repoContext.WorkDir, "Initial commit", gitproviderv2.CommitAllowEmpty()); err != nil { + if err := h.gitProviderNoAuth.Commit(ctx, repoContext.WorkDir, "Initial commit", gitproviderv2.CommitAllowEmpty()); err != nil { return fmt.Errorf("failed to create Initial commit: %w", err) } @@ -477,23 +468,12 @@ func (h *PutProject) notEmptyProjectProvisioning(ctx context.Context, codebase * return fmt.Errorf("failed to build repo url: %w", err) } - repu, repp, exists, err := codebaseutil.GetRepositoryCredentialsIfExists(ctx, codebase, h.k8sClient) + repoUsername, repoPassword, _, err := codebaseutil.GetRepositoryCredentialsIfExists(ctx, codebase, h.k8sClient) if err != nil { return fmt.Errorf("failed to get repository credentials for project provisioning: %w", err) } - if exists { - cloneGitProvider := h.gitProviderFactory(gitproviderv2.Config{ - Username: repu, - Token: repp, - }) - - if err := cloneGitProvider.CheckPermissions(ctx, repoUrl); err != nil { - return fmt.Errorf("failed to get access to the repository %s for user %s: %w", repoUrl, repu, err) - } - } - - if err = h.tryToCloneRepo(ctx, repoUrl, repu, repp, repoContext); err != nil { + if err = h.tryToCloneRepo(ctx, repoUrl, repoUsername, repoPassword, repoContext); err != nil { return fmt.Errorf("failed to clone template project: %w", err) } diff --git a/controllers/codebase/service/chain/put_project_test.go b/controllers/codebase/service/chain/put_project_test.go index 97b3767a..97e956aa 100644 --- a/controllers/codebase/service/chain/put_project_test.go +++ b/controllers/codebase/service/chain/put_project_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/go-logr/logr" - "github.com/stretchr/testify/assert" testify "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -19,1498 +18,1218 @@ import ( codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1" "github.com/epam/edp-codebase-operator/v2/pkg/gerrit" - gerritmocks "github.com/epam/edp-codebase-operator/v2/pkg/gerrit/mocks" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" - v2mocks "github.com/epam/edp-codebase-operator/v2/pkg/git/v2/mocks" + gerritMocks "github.com/epam/edp-codebase-operator/v2/pkg/gerrit/mocks" + gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git" + gitmocks "github.com/epam/edp-codebase-operator/v2/pkg/git/mocks" "github.com/epam/edp-codebase-operator/v2/pkg/gitprovider" - gitprovidermock "github.com/epam/edp-codebase-operator/v2/pkg/gitprovider/mocks" + "github.com/epam/edp-codebase-operator/v2/pkg/gitprovider/mocks" "github.com/epam/edp-codebase-operator/v2/pkg/util" ) func TestPutProject_ServeRequest(t *testing.T) { - t.Skip("We need to refactor ServeRequest method and rewrite this test accordingly") - scheme := runtime.NewScheme() require.NoError(t, codebaseApi.AddToScheme(scheme)) require.NoError(t, corev1.AddToScheme(scheme)) - defaultNs := "default" - gerritGitServer := &codebaseApi.GitServer{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gerrit", - Namespace: defaultNs, - }, - Spec: codebaseApi.GitServerSpec{ - GitProvider: codebaseApi.GitProviderGerrit, - NameSshKeySecret: "gerrit-ssh-key", - GitUser: "ci", - }, - } - gerritGitServerSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: gerritGitServer.Spec.NameSshKeySecret, - Namespace: defaultNs, - }, - } - githubGitServer := &codebaseApi.GitServer{ - ObjectMeta: metav1.ObjectMeta{ - Name: "github", - Namespace: defaultNs, - }, - Spec: codebaseApi.GitServerSpec{ - GitProvider: codebaseApi.GitProviderGithub, - NameSshKeySecret: "github-ssh-key", - }, - } - githubGitServerSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: githubGitServer.Spec.NameSshKeySecret, - Namespace: defaultNs, - }, - } - gitlabGitServer := &codebaseApi.GitServer{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gitlab", - Namespace: defaultNs, - }, - Spec: codebaseApi.GitServerSpec{ - GitProvider: codebaseApi.GitProviderGitlab, - NameSshKeySecret: "gitlab-ssh-key", - }, - } - gitlabGitServerSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: gitlabGitServer.Spec.NameSshKeySecret, - Namespace: defaultNs, - }, - } - defaultGitProvider := func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { - return func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { - return gitprovidermock.NewMockGitProjectProvider(t), nil - } - } + const defaultNs = "default" tests := []struct { - name string - codebase *codebaseApi.Codebase - objects []client.Object - gitProviderFactory func(t *testing.T) gitproviderv2.GitProviderFactory - gerritClient func(t *testing.T) gerrit.Client - gitProvider func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) - createGitProviderWithConfig func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git - wantErr require.ErrorAssertionFunc - wantStatus func(t *testing.T, status *codebaseApi.CodebaseStatus) + name string + codebase *codebaseApi.Codebase + objects []client.Object + gitProviderFactory func(t *testing.T) gitproviderv2.GitProviderFactory + gitProvider func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) + wantErr require.ErrorAssertionFunc + wantStatus func(t *testing.T, status codebaseApi.CodebaseStatus) + wantCodebaseErrorStatus func(t *testing.T, codebase *codebaseApi.Codebase) }{ { - name: "gerrit, create strategy - should put project successfully with branch to copy in default branch", + name: "skip for Import strategy", codebase: &codebaseApi.Codebase{ ObjectMeta: metav1.ObjectMeta{ - Name: "go app", + Name: "test-app", Namespace: defaultNs, }, Spec: codebaseApi.CodebaseSpec{ - Strategy: codebaseApi.Create, - GitServer: gerritGitServer.Name, - GitUrlPath: "/owner/go-repo", - DefaultBranch: "master", - BranchToCopyInDefaultBranch: "main", + Strategy: codebaseApi.Import, }, }, - objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - // Create a single mock that will be returned each time the factory is called - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Init", testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Commit", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("GetCurrentBranchName", testify.Anything, testify.Anything). - Maybe(). - Return("feature", nil). - On("RemoveBranch", testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("CreateChildBranch", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Checkout", testify.Anything, testify.Anything, testify.Anything, false). - Maybe(). - Return(nil). - On("AddRemoteLink", testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Push", testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil) - - return func(cfg gitproviderv2.Config) gitproviderv2.Git { - return mock + return func(config gitproviderv2.Config) gitproviderv2.Git { + return nil } }, - gerritClient: func(t *testing.T) gerrit.Client { - mock := gerritmocks.NewMockClient(t) - - mock. - On( - "CheckProjectExist", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). - Return(false, nil). - On( - "CreateProject", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). - Return(nil). - On( - "SetHeadToBranch", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - testify.Anything, - ). - Return(nil) - - return mock + gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return nil }, - gitProvider: defaultGitProvider, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { - // Create a single mock that will be returned each time - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Checkout", testify.Anything, testify.Anything, testify.Anything, false). - Maybe(). - Return(nil). - On("Init", testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Commit", testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil) - + wantErr: require.NoError, + wantStatus: func(t *testing.T, status codebaseApi.CodebaseStatus) { + require.Empty(t, status.Git) + }, + }, + { + name: "skip when already pushed with ProjectPushedStatus", + codebase: &codebaseApi.Codebase{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-app", + Namespace: defaultNs, + }, + Spec: codebaseApi.CodebaseSpec{ + Strategy: codebaseApi.Clone, + }, + Status: codebaseApi.CodebaseStatus{ + Git: util.ProjectPushedStatus, + }, + }, + gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { return func(config gitproviderv2.Config) gitproviderv2.Git { - return mock + return nil + } + }, + gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return nil + }, + wantErr: require.NoError, + wantStatus: func(t *testing.T, status codebaseApi.CodebaseStatus) { + require.Equal(t, util.ProjectPushedStatus, status.Git) + }, + }, + { + name: "skip when already pushed with ProjectGitLabCIPushedStatus", + codebase: &codebaseApi.Codebase{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-app", + Namespace: defaultNs, + }, + Spec: codebaseApi.CodebaseSpec{ + Strategy: codebaseApi.Create, + }, + Status: codebaseApi.CodebaseStatus{ + Git: util.ProjectGitLabCIPushedStatus, + }, + }, + gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { + return func(config gitproviderv2.Config) gitproviderv2.Git { + return nil + } + }, + gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return nil + }, + wantErr: require.NoError, + wantStatus: func(t *testing.T, status codebaseApi.CodebaseStatus) { + require.Equal(t, util.ProjectGitLabCIPushedStatus, status.Git) + }, + }, + { + name: "skip when already pushed with ProjectTemplatesPushedStatus", + codebase: &codebaseApi.Codebase{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-app", + Namespace: defaultNs, + }, + Spec: codebaseApi.CodebaseSpec{ + Strategy: codebaseApi.Clone, + }, + Status: codebaseApi.CodebaseStatus{ + Git: util.ProjectTemplatesPushedStatus, + }, + }, + gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { + return func(config gitproviderv2.Config) gitproviderv2.Git { + return nil } }, + gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return nil + }, wantErr: require.NoError, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, util.ProjectPushedStatus, status.Git) + wantStatus: func(t *testing.T, status codebaseApi.CodebaseStatus) { + require.Equal(t, util.ProjectTemplatesPushedStatus, status.Git) }, }, { - name: "gerrit, create strategy - should put empty project successfully", + name: "successfully create empty project with third-party git provider", codebase: &codebaseApi.Codebase{ ObjectMeta: metav1.ObjectMeta{ - Name: "go app", + Name: "test-app", Namespace: defaultNs, }, Spec: codebaseApi.CodebaseSpec{ Strategy: codebaseApi.Create, - GitServer: gerritGitServer.Name, - GitUrlPath: "/owner/go-repo", - DefaultBranch: "master", + GitServer: "gitlab", + GitUrlPath: "/test-app", + DefaultBranch: "main", EmptyProject: true, }, }, - objects: []client.Object{gerritGitServer, gerritGitServerSecret}, + objects: []client.Object{ + &codebaseApi.GitServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gitlab", + Namespace: defaultNs, + }, + Spec: codebaseApi.GitServerSpec{ + GitProvider: codebaseApi.GitProviderGitlab, + GitHost: "gitlab.example.com", + GitUser: "edp-ci", + NameSshKeySecret: "gitlab-access-token", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gitlab-access-token", + Namespace: defaultNs, + }, + Data: map[string][]byte{ + "token": []byte("fake-token"), + }, + }, + }, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - // Create a single mock that will be returned each time the factory is called - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("GetCurrentBranchName", testify.Anything, testify.Anything). - Maybe(). - Return("master", nil). - On("Checkout", testify.Anything, testify.Anything, testify.Anything, false). - Maybe(). - Return(nil). - On("AddRemoteLink", testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Push", testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil) - - return func(cfg gitproviderv2.Config) gitproviderv2.Git { + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Commit(testify.Anything, testify.Anything, "Initial commit", testify.Anything).Return(nil) + mock.EXPECT().GetCurrentBranchName(testify.Anything, testify.Anything).Return("main", nil) + mock.EXPECT().AddRemoteLink(testify.Anything, testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Push(testify.Anything, testify.Anything, testify.Anything, testify.Anything).Return(nil) + return func(config gitproviderv2.Config) gitproviderv2.Git { return mock } }, - gerritClient: func(t *testing.T) gerrit.Client { - mock := gerritmocks.NewMockClient(t) - - mock. - On( - "CheckProjectExist", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). - Return(false, nil). - On( - "CreateProject", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). - Return(nil). - On( - "SetHeadToBranch", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - testify.Anything, - ). + gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + mock := mocks.NewMockGitProjectProvider(t) + mock.EXPECT().ProjectExists(testify.Anything, testify.Anything, testify.Anything, "test-app"). + Return(false, nil) + mock.EXPECT().CreateProject(testify.Anything, testify.Anything, testify.Anything, "test-app", testify.Anything). Return(nil) - - return mock + mock.EXPECT().SetDefaultBranch(testify.Anything, testify.Anything, testify.Anything, "test-app", "main"). + Return(nil) + return func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return mock, nil + } + }, + wantErr: require.NoError, + wantStatus: func(t *testing.T, status codebaseApi.CodebaseStatus) { + require.Equal(t, util.ProjectPushedStatus, status.Git) + }, + }, + { + name: "successfully create empty project if project already exists in third-party git provider", + codebase: &codebaseApi.Codebase{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-app", + Namespace: defaultNs, + }, + Spec: codebaseApi.CodebaseSpec{ + Strategy: codebaseApi.Create, + GitServer: "github", + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: true, + }, + }, + objects: []client.Object{ + &codebaseApi.GitServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github", + Namespace: defaultNs, + }, + Spec: codebaseApi.GitServerSpec{ + GitProvider: codebaseApi.GitProviderGithub, + GitHost: "github.com", + GitUser: "edp-ci", + NameSshKeySecret: "github-access-token", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-access-token", + Namespace: defaultNs, + }, + Data: map[string][]byte{ + "token": []byte("fake-token"), + }, + }, }, - gitProvider: defaultGitProvider, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { + gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Commit(testify.Anything, testify.Anything, "Initial commit", testify.Anything).Return(nil) + mock.EXPECT().GetCurrentBranchName(testify.Anything, testify.Anything).Return("main", nil) + mock.EXPECT().AddRemoteLink(testify.Anything, testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Push(testify.Anything, testify.Anything, testify.Anything, testify.Anything).Return(nil) return func(config gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("Init", testify.Anything, testify.Anything). - Return(nil). - On("Commit", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil) - return mock } }, + gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + mock := mocks.NewMockGitProjectProvider(t) + mock.EXPECT().ProjectExists(testify.Anything, testify.Anything, testify.Anything, "test-app"). + Return(true, nil) + mock.EXPECT().SetDefaultBranch(testify.Anything, testify.Anything, testify.Anything, "test-app", "main"). + Return(nil) + return func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return mock, nil + } + }, wantErr: require.NoError, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, util.ProjectPushedStatus, status.Git) + wantStatus: func(t *testing.T, status codebaseApi.CodebaseStatus) { + require.Equal(t, util.ProjectPushedStatus, status.Git) }, }, { - name: "gerrit, clone strategy - should put project successfully", + name: "successfully create non-empty project with third-party git provider", codebase: &codebaseApi.Codebase{ ObjectMeta: metav1.ObjectMeta{ - Name: "go app", + Name: "test-app", Namespace: defaultNs, }, Spec: codebaseApi.CodebaseSpec{ - Strategy: codebaseApi.Clone, - GitServer: gerritGitServer.Name, - GitUrlPath: "/owner/go-repo", - Repository: &codebaseApi.Repository{Url: "https://github.com/owner/repo.git"}, - DefaultBranch: "master", + Strategy: codebaseApi.Create, + GitServer: "gitlab", + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: false, + Lang: "go", + BuildTool: "go", + Framework: "beego", + }, + }, + objects: []client.Object{ + &codebaseApi.GitServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gitlab", + Namespace: defaultNs, + }, + Spec: codebaseApi.GitServerSpec{ + GitProvider: codebaseApi.GitProviderGitlab, + GitHost: "gitlab.example.com", + GitUser: "edp-ci", + NameSshKeySecret: "gitlab-access-token", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gitlab-access-token", + Namespace: defaultNs, + }, + Data: map[string][]byte{ + "token": []byte("fake-token"), + }, }, }, - objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - // Create a single mock that will be returned each time the factory is called - mock := v2mocks.NewMockGit(t) - - mock. - On("CheckPermissions", testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("GetCurrentBranchName", testify.Anything, testify.Anything). - Maybe(). - Return("feature", nil). - On("Checkout", testify.Anything, testify.Anything, testify.Anything, true). - Maybe(). - Return(nil). - On("AddRemoteLink", testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Push", testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil) - - return func(cfg gitproviderv2.Config) gitproviderv2.Git { + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Clone(testify.Anything, "https://github.com/epmd-edp/go-go-beego.git", testify.Anything).Return(nil) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Commit(testify.Anything, testify.Anything, "Initial commit", testify.Anything).Return(nil) + mock.EXPECT().GetCurrentBranchName(testify.Anything, testify.Anything).Return("main", nil) + mock.EXPECT().AddRemoteLink(testify.Anything, testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Push(testify.Anything, testify.Anything, testify.Anything, testify.Anything).Return(nil) + return func(config gitproviderv2.Config) gitproviderv2.Git { return mock } }, - gerritClient: func(t *testing.T) gerrit.Client { - mock := gerritmocks.NewMockClient(t) - - mock. - On( - "CheckProjectExist", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). - Return(false, nil). - On( - "CreateProject", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). - Return(nil). - On( - "SetHeadToBranch", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - testify.Anything, - ). + gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + mock := mocks.NewMockGitProjectProvider(t) + mock.EXPECT().ProjectExists(testify.Anything, testify.Anything, testify.Anything, "test-app"). + Return(false, nil) + mock.EXPECT().CreateProject(testify.Anything, testify.Anything, testify.Anything, "test-app", testify.Anything). Return(nil) - - return mock - }, - gitProvider: defaultGitProvider, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { - // Create a single mock that will be returned each time - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Maybe(). + mock.EXPECT().SetDefaultBranch(testify.Anything, testify.Anything, testify.Anything, "test-app", "main"). Return(nil) - - return func(config gitproviderv2.Config) gitproviderv2.Git { - return mock + return func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return mock, nil } }, wantErr: require.NoError, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, util.ProjectPushedStatus, status.Git) + wantStatus: func(t *testing.T, status codebaseApi.CodebaseStatus) { + require.Equal(t, util.ProjectPushedStatus, status.Git) }, }, { - name: "gerrit, clone strategy - failed to set head to branch", + name: "failed to check if project exists in third-party git provider", codebase: &codebaseApi.Codebase{ ObjectMeta: metav1.ObjectMeta{ - Name: "go app", + Name: "test-app", Namespace: defaultNs, }, Spec: codebaseApi.CodebaseSpec{ - Strategy: codebaseApi.Clone, - GitServer: gerritGitServer.Name, - GitUrlPath: "/owner/go-repo", - Repository: &codebaseApi.Repository{Url: "https://github.com/owner/repo.git"}, - DefaultBranch: "master", + Strategy: codebaseApi.Create, + GitServer: "gitlab", + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: true, + }, + }, + objects: []client.Object{ + &codebaseApi.GitServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gitlab", + Namespace: defaultNs, + }, + Spec: codebaseApi.GitServerSpec{ + GitProvider: codebaseApi.GitProviderGitlab, + GitHost: "gitlab.example.com", + GitUser: "edp-ci", + NameSshKeySecret: "gitlab-access-token", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gitlab-access-token", + Namespace: defaultNs, + }, + Data: map[string][]byte{ + "token": []byte("fake-token"), + }, }, }, - objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - // Create a single mock that will be returned each time the factory is called - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("GetCurrentBranchName", testify.Anything, testify.Anything). - Maybe(). - Return("feature", nil). - On("Checkout", testify.Anything, testify.Anything, testify.Anything, true). - Maybe(). - Return(nil). - On("AddRemoteLink", testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Push", testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil) - - return func(cfg gitproviderv2.Config) gitproviderv2.Git { + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Commit(testify.Anything, testify.Anything, "Initial commit", testify.Anything).Return(nil) + mock.EXPECT().GetCurrentBranchName(testify.Anything, testify.Anything).Return("main", nil) + return func(config gitproviderv2.Config) gitproviderv2.Git { return mock } }, - gerritClient: func(t *testing.T) gerrit.Client { - mock := gerritmocks.NewMockClient(t) - - mock. - On( - "CheckProjectExist", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). - Return(false, nil). - On( - "CreateProject", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). - Return(nil). - On( - "SetHeadToBranch", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - testify.Anything, - ). - Return(errors.New("failed to set head to branch")) - - return mock + gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + mock := mocks.NewMockGitProjectProvider(t) + mock.EXPECT().ProjectExists(testify.Anything, testify.Anything, testify.Anything, "test-app"). + Return(false, errors.New("API connection failed")) + return func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return mock, nil + } + }, + wantErr: require.Error, + wantCodebaseErrorStatus: func(t *testing.T, codebase *codebaseApi.Codebase) { + require.Equal(t, util.StatusFailed, codebase.Status.Status) + require.Contains(t, codebase.Status.DetailedMessage, "failed to check if project exists") + }, + }, + { + name: "failed to create project in third-party git provider", + codebase: &codebaseApi.Codebase{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-app", + Namespace: defaultNs, + }, + Spec: codebaseApi.CodebaseSpec{ + Strategy: codebaseApi.Create, + GitServer: "github", + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: true, + }, + }, + objects: []client.Object{ + &codebaseApi.GitServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github", + Namespace: defaultNs, + }, + Spec: codebaseApi.GitServerSpec{ + GitProvider: codebaseApi.GitProviderGithub, + GitHost: "github.com", + GitUser: "edp-ci", + NameSshKeySecret: "github-access-token", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-access-token", + Namespace: defaultNs, + }, + Data: map[string][]byte{ + "token": []byte("fake-token"), + }, + }, }, - gitProvider: defaultGitProvider, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { + gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Commit(testify.Anything, testify.Anything, "Initial commit", testify.Anything).Return(nil) + mock.EXPECT().GetCurrentBranchName(testify.Anything, testify.Anything).Return("main", nil) return func(config gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil) - return mock } }, - wantErr: func(t require.TestingT, err error, i ...interface{}) { - require.Error(t, err) - - assert.Contains(t, err.Error(), "failed to set head to branch") + gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + mock := mocks.NewMockGitProjectProvider(t) + mock.EXPECT().ProjectExists(testify.Anything, testify.Anything, testify.Anything, "test-app"). + Return(false, nil) + mock.EXPECT().CreateProject(testify.Anything, testify.Anything, testify.Anything, "test-app", testify.Anything). + Return(errors.New("permission denied")) + return func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return mock, nil + } }, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, "", status.Git) - assert.Equal(t, util.StatusFailed, status.Status) - assert.Contains(t, status.DetailedMessage, "failed to set head to branch") + wantErr: require.Error, + wantCodebaseErrorStatus: func(t *testing.T, codebase *codebaseApi.Codebase) { + require.Equal(t, util.StatusFailed, codebase.Status.Status) + require.Contains(t, codebase.Status.DetailedMessage, "failed to create project") }, }, { - name: "gerrit, clone strategy - failed to push project", + name: "failed to push to third-party git provider", codebase: &codebaseApi.Codebase{ ObjectMeta: metav1.ObjectMeta{ - Name: "go app", + Name: "test-app", Namespace: defaultNs, }, Spec: codebaseApi.CodebaseSpec{ - Strategy: codebaseApi.Clone, - GitServer: gerritGitServer.Name, - GitUrlPath: "/owner/go-repo", - Repository: &codebaseApi.Repository{Url: "https://github.com/owner/repo.git"}, - DefaultBranch: "master", + Strategy: codebaseApi.Create, + GitServer: "gitlab", + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: true, + }, + }, + objects: []client.Object{ + &codebaseApi.GitServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gitlab", + Namespace: defaultNs, + }, + Spec: codebaseApi.GitServerSpec{ + GitProvider: codebaseApi.GitProviderGitlab, + GitHost: "gitlab.example.com", + GitUser: "edp-ci", + NameSshKeySecret: "gitlab-access-token", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gitlab-access-token", + Namespace: defaultNs, + }, + Data: map[string][]byte{ + "token": []byte("fake-token"), + }, }, }, - objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - // Create a single mock that will be returned each time the factory is called - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("GetCurrentBranchName", testify.Anything, testify.Anything). - Maybe(). - Return("feature", nil). - On("Checkout", testify.Anything, testify.Anything, testify.Anything, true). - Maybe(). - Return(nil). - On("AddRemoteLink", testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Push", testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(errors.New("failed to push changes")) - - return func(cfg gitproviderv2.Config) gitproviderv2.Git { + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Commit(testify.Anything, testify.Anything, "Initial commit", testify.Anything).Return(nil) + mock.EXPECT().GetCurrentBranchName(testify.Anything, testify.Anything).Return("main", nil) + mock.EXPECT().AddRemoteLink(testify.Anything, testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Push(testify.Anything, testify.Anything, testify.Anything, testify.Anything). + Return(errors.New("push rejected")) + return func(config gitproviderv2.Config) gitproviderv2.Git { return mock } }, - gerritClient: func(t *testing.T) gerrit.Client { - mock := gerritmocks.NewMockClient(t) - - mock. - On( - "CheckProjectExist", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). - Return(false, nil). - On( - "CreateProject", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). + gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + mock := mocks.NewMockGitProjectProvider(t) + mock.EXPECT().ProjectExists(testify.Anything, testify.Anything, testify.Anything, "test-app"). + Return(false, nil) + mock.EXPECT().CreateProject(testify.Anything, testify.Anything, testify.Anything, "test-app", testify.Anything). Return(nil) - - return mock + return func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return mock, nil + } }, - gitProvider: defaultGitProvider, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { - // Create a single mock that will be returned each time - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Maybe(). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Maybe(). - Return(nil) - + wantErr: require.Error, + wantCodebaseErrorStatus: func(t *testing.T, codebase *codebaseApi.Codebase) { + require.Equal(t, util.StatusFailed, codebase.Status.Status) + require.Contains(t, codebase.Status.DetailedMessage, "failed to push changes and tags into git") + }, + }, + { + name: "failed to set default branch in third-party git provider", + codebase: &codebaseApi.Codebase{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-app", + Namespace: defaultNs, + }, + Spec: codebaseApi.CodebaseSpec{ + Strategy: codebaseApi.Create, + GitServer: "github", + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: true, + }, + }, + objects: []client.Object{ + &codebaseApi.GitServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github", + Namespace: defaultNs, + }, + Spec: codebaseApi.GitServerSpec{ + GitProvider: codebaseApi.GitProviderGithub, + GitHost: "github.com", + GitUser: "edp-ci", + NameSshKeySecret: "github-access-token", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-access-token", + Namespace: defaultNs, + }, + Data: map[string][]byte{ + "token": []byte("fake-token"), + }, + }, + }, + gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Commit(testify.Anything, testify.Anything, "Initial commit", testify.Anything).Return(nil) + mock.EXPECT().GetCurrentBranchName(testify.Anything, testify.Anything).Return("main", nil) + mock.EXPECT().AddRemoteLink(testify.Anything, testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Push(testify.Anything, testify.Anything, testify.Anything, testify.Anything).Return(nil) return func(config gitproviderv2.Config) gitproviderv2.Git { return mock } }, - wantErr: func(t require.TestingT, err error, i ...interface{}) { - require.Error(t, err) - - assert.Contains(t, err.Error(), "failed to push changes") + gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + mock := mocks.NewMockGitProjectProvider(t) + mock.EXPECT().ProjectExists(testify.Anything, testify.Anything, testify.Anything, "test-app"). + Return(false, nil) + mock.EXPECT().CreateProject(testify.Anything, testify.Anything, testify.Anything, "test-app", testify.Anything). + Return(nil) + mock.EXPECT().SetDefaultBranch(testify.Anything, testify.Anything, testify.Anything, "test-app", "main"). + Return(errors.New("branch does not exist")) + return func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return mock, nil + } }, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, "", status.Git) - assert.Equal(t, util.StatusFailed, status.Status) - assert.Contains(t, status.DetailedMessage, "failed to push changes") + wantErr: require.Error, + wantCodebaseErrorStatus: func(t *testing.T, codebase *codebaseApi.Codebase) { + require.Equal(t, util.StatusFailed, codebase.Status.Status) + require.Contains(t, codebase.Status.DetailedMessage, "failed to set default branch") }, }, { - name: "gerrit, clone strategy - failed create project", + name: "successfully create project when SetDefaultBranch is not supported by git provider", codebase: &codebaseApi.Codebase{ ObjectMeta: metav1.ObjectMeta{ - Name: "go app", + Name: "test-app", Namespace: defaultNs, }, Spec: codebaseApi.CodebaseSpec{ - Strategy: codebaseApi.Clone, - GitServer: gerritGitServer.Name, - GitUrlPath: "/owner/go-repo", - Repository: &codebaseApi.Repository{Url: "https://github.com/owner/repo.git"}, - DefaultBranch: "master", + Strategy: codebaseApi.Create, + GitServer: "gitlab", + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: true, + }, + }, + objects: []client.Object{ + &codebaseApi.GitServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gitlab", + Namespace: defaultNs, + }, + Spec: codebaseApi.GitServerSpec{ + GitProvider: codebaseApi.GitProviderGitlab, + GitHost: "gitlab.example.com", + GitUser: "edp-ci", + NameSshKeySecret: "gitlab-access-token", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gitlab-access-token", + Namespace: defaultNs, + }, + Data: map[string][]byte{ + "token": []byte("fake-token"), + }, }, }, - objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(cfg gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("GetCurrentBranchName", testify.Anything, testify.Anything). - Return("feature", nil). - On("Checkout", testify.Anything, testify.Anything, testify.Anything, true). - Return(nil) - + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Commit(testify.Anything, testify.Anything, "Initial commit", testify.Anything).Return(nil) + mock.EXPECT().GetCurrentBranchName(testify.Anything, testify.Anything).Return("main", nil) + mock.EXPECT().AddRemoteLink(testify.Anything, testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Push(testify.Anything, testify.Anything, testify.Anything, testify.Anything).Return(nil) + return func(config gitproviderv2.Config) gitproviderv2.Git { return mock } }, - gerritClient: func(t *testing.T) gerrit.Client { - mock := gerritmocks.NewMockClient(t) - - mock. - On( - "CheckProjectExist", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). - Return(false, nil). - On( - "CreateProject", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). - Return(errors.New("failed to create project")) - - return mock + gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + mock := mocks.NewMockGitProjectProvider(t) + mock.EXPECT().ProjectExists(testify.Anything, testify.Anything, testify.Anything, "test-app"). + Return(false, nil) + mock.EXPECT().CreateProject(testify.Anything, testify.Anything, testify.Anything, "test-app", testify.Anything). + Return(nil) + mock.EXPECT().SetDefaultBranch(testify.Anything, testify.Anything, testify.Anything, "test-app", "main"). + Return(gitprovider.ErrApiNotSupported) + return func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return mock, nil + } }, - gitProvider: defaultGitProvider, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { + wantErr: require.NoError, + wantStatus: func(t *testing.T, status codebaseApi.CodebaseStatus) { + require.Equal(t, util.ProjectPushedStatus, status.Git) + }, + }, + { + name: "failed to clone template repository for non-empty project", + codebase: &codebaseApi.Codebase{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-app", + Namespace: defaultNs, + }, + Spec: codebaseApi.CodebaseSpec{ + Strategy: codebaseApi.Create, + GitServer: "github", + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: false, + Lang: "go", + BuildTool: "go", + Framework: "beego", + }, + }, + objects: []client.Object{ + &codebaseApi.GitServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github", + Namespace: defaultNs, + }, + Spec: codebaseApi.GitServerSpec{ + GitProvider: codebaseApi.GitProviderGithub, + GitHost: "github.com", + GitUser: "edp-ci", + NameSshKeySecret: "github-access-token", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-access-token", + Namespace: defaultNs, + }, + Data: map[string][]byte{ + "token": []byte("fake-token"), + }, + }, + }, + gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Clone(testify.Anything, "https://github.com/epmd-edp/go-go-beego.git", testify.Anything). + Return(errors.New("repository not found")) return func(config gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil) - return mock } }, - wantErr: func(t require.TestingT, err error, i ...interface{}) { - require.Error(t, err) - - assert.Contains(t, err.Error(), "failed to create project") + gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return mocks.NewMockGitProjectProvider(t), nil + } }, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, "", status.Git) - assert.Equal(t, util.StatusFailed, status.Status) - assert.Contains(t, status.DetailedMessage, "failed to create project") + wantErr: require.Error, + wantCodebaseErrorStatus: func(t *testing.T, codebase *codebaseApi.Codebase) { + require.Equal(t, util.StatusFailed, codebase.Status.Status) + require.Contains(t, codebase.Status.DetailedMessage, "failed to clone template project") }, }, { - name: "gerrit, clone strategy - failed to add remote link", + name: "failed to init empty repository", codebase: &codebaseApi.Codebase{ ObjectMeta: metav1.ObjectMeta{ - Name: "go app", + Name: "test-app", Namespace: defaultNs, }, Spec: codebaseApi.CodebaseSpec{ - Strategy: codebaseApi.Clone, - GitServer: gerritGitServer.Name, - GitUrlPath: "/owner/go-repo", - Repository: &codebaseApi.Repository{Url: "https://github.com/owner/repo.git"}, - DefaultBranch: "master", + Strategy: codebaseApi.Create, + GitServer: "gitlab", + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: true, + }, + }, + objects: []client.Object{ + &codebaseApi.GitServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gitlab", + Namespace: defaultNs, + }, + Spec: codebaseApi.GitServerSpec{ + GitProvider: codebaseApi.GitProviderGitlab, + GitHost: "gitlab.example.com", + GitUser: "edp-ci", + NameSshKeySecret: "gitlab-access-token", + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gitlab-access-token", + Namespace: defaultNs, + }, + Data: map[string][]byte{ + "token": []byte("fake-token"), + }, }, }, - objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(cfg gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("GetCurrentBranchName", testify.Anything, testify.Anything). - Return("feature", nil). - On("Checkout", testify.Anything, testify.Anything, testify.Anything, true). - Return(nil). - On("AddRemoteLink", testify.Anything, testify.Anything, testify.Anything). - Return(errors.New("failed to add remote link")) - + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(errors.New("failed to initialize git repository")) + return func(config gitproviderv2.Config) gitproviderv2.Git { return mock } }, - gerritClient: func(t *testing.T) gerrit.Client { - mock := gerritmocks.NewMockClient(t) + gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return mocks.NewMockGitProjectProvider(t), nil + } + }, + wantErr: require.Error, + wantCodebaseErrorStatus: func(t *testing.T, codebase *codebaseApi.Codebase) { + require.Equal(t, util.StatusFailed, codebase.Status.Status) + require.Contains(t, codebase.Status.DetailedMessage, "failed to create empty git repository") + }, + }, + } - mock. - On( - "CheckProjectExist", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). - Return(false, nil). - On( - "CreateProject", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). - Return(nil) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv(util.WorkDirEnv, t.TempDir()) - return mock - }, - gitProvider: defaultGitProvider, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { - return func(config gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) + k8sClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(tt.codebase). + WithObjects(tt.objects...). + WithStatusSubresource(tt.codebase). + WithStatusSubresource(tt.objects...). + Build() - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil) + h := NewPutProject( + k8sClient, + gerritMocks.NewMockClient(t), + tt.gitProvider(t), + tt.gitProviderFactory(t), + ) - return mock - } - }, - wantErr: func(t require.TestingT, err error, i ...interface{}) { - require.Error(t, err) + err := h.ServeRequest(ctrl.LoggerInto(context.Background(), logr.Discard()), tt.codebase) - assert.Contains(t, err.Error(), "failed to add remote link") - }, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, "", status.Git) - assert.Equal(t, util.StatusFailed, status.Status) - assert.Contains(t, status.DetailedMessage, "failed to add remote link") - }, + tt.wantErr(t, err) + + processedCodebase := &codebaseApi.Codebase{} + + require.NoError(t, + k8sClient.Get( + context.Background(), + types.NamespacedName{ + Name: tt.codebase.Name, + Namespace: tt.codebase.Namespace, + }, + processedCodebase, + ), + ) + + if tt.wantCodebaseErrorStatus != nil { + tt.wantCodebaseErrorStatus(t, tt.codebase) + } + + if tt.wantStatus != nil { + tt.wantStatus(t, processedCodebase.Status) + } + }) + } +} + +func TestPutProject_ServeRequest_Gerrit(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, codebaseApi.AddToScheme(scheme)) + require.NoError(t, corev1.AddToScheme(scheme)) + + defaultNs := "default" + gerritGitServer := &codebaseApi.GitServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gerrit", + Namespace: defaultNs, + }, + Spec: codebaseApi.GitServerSpec{ + GitProvider: codebaseApi.GitProviderGerrit, + GitHost: "gerrit.example.com", + GitUser: "ci", + SshPort: 29418, + NameSshKeySecret: "gerrit-ssh-key", + }, + } + gerritGitServerSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: gerritGitServer.Spec.NameSshKeySecret, + Namespace: defaultNs, }, + Data: map[string][]byte{ + util.PrivateSShKeyName: []byte("fake-ssh-key"), + }, + } + + tests := []struct { + name string + codebase *codebaseApi.Codebase + objects []client.Object + gitProviderFactory func(t *testing.T) gitproviderv2.GitProviderFactory + gerritClient func(t *testing.T) gerrit.Client + wantErr require.ErrorAssertionFunc + wantStatus func(t *testing.T, status codebaseApi.CodebaseStatus) + wantCodebaseErrorStatus func(t *testing.T, codebase *codebaseApi.Codebase) + }{ { - name: "gerrit, clone strategy - failed to create project", + name: "successfully create empty Gerrit project", codebase: &codebaseApi.Codebase{ ObjectMeta: metav1.ObjectMeta{ - Name: "go app", + Name: "test-app", Namespace: defaultNs, }, Spec: codebaseApi.CodebaseSpec{ - Strategy: codebaseApi.Clone, + Strategy: codebaseApi.Create, GitServer: gerritGitServer.Name, - GitUrlPath: "/owner/go-repo", - Repository: &codebaseApi.Repository{Url: "https://github.com/owner/repo.git"}, - DefaultBranch: "master", + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: true, }, }, objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(cfg gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("GetCurrentBranchName", testify.Anything, testify.Anything). - Return("feature", nil). - On("Checkout", testify.Anything, testify.Anything, testify.Anything, true). - Return(nil) - + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Commit(testify.Anything, testify.Anything, "Initial commit", testify.Anything).Return(nil) + mock.EXPECT().AddRemoteLink(testify.Anything, testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Push(testify.Anything, testify.Anything, testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().GetCurrentBranchName(testify.Anything, testify.Anything).Return("main", nil) + return func(config gitproviderv2.Config) gitproviderv2.Git { return mock } }, gerritClient: func(t *testing.T) gerrit.Client { - mock := gerritmocks.NewMockClient(t) - - mock. - On( - "CheckProjectExist", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). - Return(false, nil). - On( - "CreateProject", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). - Return(errors.New("failed to create project")) - + mock := gerritMocks.NewMockClient(t) + mock.EXPECT().CheckProjectExist(int32(29418), "fake-ssh-key", "gerrit.example.com", "ci", "test-app", testify.Anything). + Return(false, nil) + mock.EXPECT().CreateProject(int32(29418), "fake-ssh-key", "gerrit.example.com", "ci", "test-app", testify.Anything). + Return(nil) + mock.EXPECT().SetHeadToBranch(int32(29418), "fake-ssh-key", "gerrit.example.com", "ci", "test-app", "main", testify.Anything). + Return(nil) return mock }, - gitProvider: defaultGitProvider, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { - return func(config gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil) - - return mock - } - }, - wantErr: func(t require.TestingT, err error, i ...interface{}) { - require.Error(t, err) - - assert.Contains(t, err.Error(), "failed to create project") - }, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, "", status.Git) - assert.Equal(t, util.StatusFailed, status.Status) - assert.Contains(t, status.DetailedMessage, "failed to create project") + wantErr: require.NoError, + wantStatus: func(t *testing.T, status codebaseApi.CodebaseStatus) { + require.Equal(t, util.ProjectPushedStatus, status.Git) }, }, { - name: "gerrit, clone strategy - failed to check project exist", + name: "successfully create empty Gerrit project if project already exists", codebase: &codebaseApi.Codebase{ ObjectMeta: metav1.ObjectMeta{ - Name: "go app", + Name: "test-app", Namespace: defaultNs, }, Spec: codebaseApi.CodebaseSpec{ - Strategy: codebaseApi.Clone, + Strategy: codebaseApi.Create, GitServer: gerritGitServer.Name, - GitUrlPath: "/owner/go-repo", - Repository: &codebaseApi.Repository{Url: "https://github.com/owner/repo.git"}, - DefaultBranch: "master", + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: true, }, }, objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(cfg gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("GetCurrentBranchName", testify.Anything, testify.Anything). - Return("feature", nil). - On("Checkout", testify.Anything, testify.Anything, testify.Anything, true). - Return(nil) - + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Commit(testify.Anything, testify.Anything, "Initial commit", testify.Anything).Return(nil) + mock.EXPECT().AddRemoteLink(testify.Anything, testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Push(testify.Anything, testify.Anything, testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().GetCurrentBranchName(testify.Anything, testify.Anything).Return("main", nil) + return func(config gitproviderv2.Config) gitproviderv2.Git { return mock } }, gerritClient: func(t *testing.T) gerrit.Client { - mock := gerritmocks.NewMockClient(t) - - mock. - On( - "CheckProjectExist", - testify.Anything, - testify.Anything, - testify.Anything, - gerritGitServer.Spec.GitUser, - testify.Anything, - testify.Anything, - ). - Return(false, errors.New("failed to check project exist")) - + mock := gerritMocks.NewMockClient(t) + mock.EXPECT().CheckProjectExist(int32(29418), "fake-ssh-key", "gerrit.example.com", "ci", "test-app", testify.Anything). + Return(true, nil) + mock.EXPECT().SetHeadToBranch(int32(29418), "fake-ssh-key", "gerrit.example.com", "ci", "test-app", "main", testify.Anything). + Return(nil) return mock }, - gitProvider: defaultGitProvider, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { - return func(config gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil) - - return mock - } - }, - wantErr: func(t require.TestingT, err error, i ...interface{}) { - require.Error(t, err) - - assert.Contains(t, err.Error(), "failed to check project exist") - }, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, "", status.Git) - assert.Equal(t, util.StatusFailed, status.Status) - assert.Contains(t, status.DetailedMessage, "failed to check project exist") + wantErr: require.NoError, + wantStatus: func(t *testing.T, status codebaseApi.CodebaseStatus) { + require.Equal(t, util.ProjectPushedStatus, status.Git) }, }, { - name: "gerrit, clone strategy - failed to get GitServer secret", + name: "successfully create non-empty Gerrit project", codebase: &codebaseApi.Codebase{ ObjectMeta: metav1.ObjectMeta{ - Name: "go app", + Name: "test-app", Namespace: defaultNs, }, Spec: codebaseApi.CodebaseSpec{ - Strategy: codebaseApi.Clone, + Strategy: codebaseApi.Create, GitServer: gerritGitServer.Name, - GitUrlPath: "/owner/go-repo", - Repository: &codebaseApi.Repository{Url: "https://github.com/owner/repo.git"}, - DefaultBranch: "master", + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: false, + Lang: "go", + BuildTool: "go", + Framework: "beego", }, }, - objects: []client.Object{gerritGitServer}, + objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(cfg gitproviderv2.Config) gitproviderv2.Git { - return v2mocks.NewMockGit(t) - } - }, - gerritClient: func(t *testing.T) gerrit.Client { - return gerritmocks.NewMockClient(t) - }, - gitProvider: defaultGitProvider, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Clone(testify.Anything, "https://github.com/epmd-edp/go-go-beego.git", testify.Anything).Return(nil) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Commit(testify.Anything, testify.Anything, "Initial commit", testify.Anything).Return(nil) + mock.EXPECT().GetCurrentBranchName(testify.Anything, testify.Anything).Return("main", nil) + mock.EXPECT().AddRemoteLink(testify.Anything, testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Push(testify.Anything, testify.Anything, testify.Anything, testify.Anything).Return(nil) return func(config gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil) - return mock } }, - wantErr: func(t require.TestingT, err error, i ...interface{}) { - require.Error(t, err) - - assert.Contains(t, err.Error(), "failed to get GitServer secret") + gerritClient: func(t *testing.T) gerrit.Client { + mock := gerritMocks.NewMockClient(t) + mock.EXPECT().CheckProjectExist(int32(29418), "fake-ssh-key", "gerrit.example.com", "ci", "test-app", testify.Anything). + Return(false, nil) + mock.EXPECT().CreateProject(int32(29418), "fake-ssh-key", "gerrit.example.com", "ci", "test-app", testify.Anything). + Return(nil) + mock.EXPECT().SetHeadToBranch(int32(29418), "fake-ssh-key", "gerrit.example.com", "ci", "test-app", "main", testify.Anything). + Return(nil) + return mock }, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, "", status.Git) - assert.Equal(t, util.StatusFailed, status.Status) - assert.Contains(t, status.DetailedMessage, "failed to get GitServer secret") + wantErr: require.NoError, + wantStatus: func(t *testing.T, status codebaseApi.CodebaseStatus) { + require.Equal(t, util.ProjectPushedStatus, status.Git) }, }, { - name: "gerrit, clone strategy - failed to checkout branch", + name: "failed to check if Gerrit project exists", codebase: &codebaseApi.Codebase{ ObjectMeta: metav1.ObjectMeta{ - Name: "go app", + Name: "test-app", Namespace: defaultNs, }, Spec: codebaseApi.CodebaseSpec{ - Strategy: codebaseApi.Clone, + Strategy: codebaseApi.Create, GitServer: gerritGitServer.Name, - GitUrlPath: "/owner/go-repo", - Repository: &codebaseApi.Repository{Url: "https://github.com/owner/repo.git"}, - DefaultBranch: "master", + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: true, }, }, objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(cfg gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("GetCurrentBranchName", testify.Anything, testify.Anything). - Return("feature", nil). - On("Checkout", testify.Anything, testify.Anything, testify.Anything, true). - Return(errors.New("failed to checkout branch")) - - return mock - } - }, - gerritClient: func(t *testing.T) gerrit.Client { - return gerritmocks.NewMockClient(t) - }, - gitProvider: defaultGitProvider, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Commit(testify.Anything, testify.Anything, "Initial commit", testify.Anything).Return(nil) + mock.EXPECT().GetCurrentBranchName(testify.Anything, testify.Anything).Return("main", nil) return func(config gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil) - return mock } }, - wantErr: func(t require.TestingT, err error, i ...interface{}) { - require.Error(t, err) - - assert.Contains(t, err.Error(), "failed to checkout branch") + gerritClient: func(t *testing.T) gerrit.Client { + mock := gerritMocks.NewMockClient(t) + mock.EXPECT().CheckProjectExist(int32(29418), "fake-ssh-key", "gerrit.example.com", "ci", "test-app", testify.Anything). + Return(false, errors.New("SSH connection failed")) + return mock }, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, "", status.Git) - assert.Equal(t, util.StatusFailed, status.Status) - assert.Contains(t, status.DetailedMessage, "failed to checkout branch") + wantErr: require.Error, + wantCodebaseErrorStatus: func(t *testing.T, codebase *codebaseApi.Codebase) { + require.Equal(t, util.StatusFailed, codebase.Status.Status) + require.Contains(t, codebase.Status.DetailedMessage, "failed to check if project exist in Gerrit") }, }, { - name: "github, create strategy - should put project successfully", + name: "failed to create Gerrit project", codebase: &codebaseApi.Codebase{ ObjectMeta: metav1.ObjectMeta{ - Name: "go app", + Name: "test-app", Namespace: defaultNs, }, Spec: codebaseApi.CodebaseSpec{ Strategy: codebaseApi.Create, - GitServer: githubGitServer.Name, - GitUrlPath: "/owner/go-repo", - DefaultBranch: "master", + GitServer: gerritGitServer.Name, + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: true, }, }, - objects: []client.Object{githubGitServer, githubGitServerSecret}, + objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(cfg gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("Init", testify.Anything, testify.Anything). - Return(nil). - On("Commit", testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("GetCurrentBranchName", testify.Anything, testify.Anything). - Return("feature", nil). - On("Checkout", testify.Anything, testify.Anything, testify.Anything, false). - Return(nil). - On("AddRemoteLink", testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("Push", testify.Anything, testify.Anything, testify.Anything). - Return(nil) - + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Commit(testify.Anything, testify.Anything, "Initial commit", testify.Anything).Return(nil) + mock.EXPECT().GetCurrentBranchName(testify.Anything, testify.Anything).Return("main", nil) + return func(config gitproviderv2.Config) gitproviderv2.Git { return mock } }, gerritClient: func(t *testing.T) gerrit.Client { - return gerritmocks.NewMockClient(t) - }, - gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { - mock := gitprovidermock.NewMockGitProjectProvider(t) - - mock.On("ProjectExists", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(false, nil). - On("CreateProject", testify.Anything, testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("SetDefaultBranch", testify.Anything, testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil) - - return func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { - return mock, nil - } - }, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { - return func(config gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("Init", testify.Anything, testify.Anything). - Return(nil). - On("Commit", testify.Anything, testify.Anything, testify.Anything). - Return(nil) - - return mock - } + mock := gerritMocks.NewMockClient(t) + mock.EXPECT().CheckProjectExist(int32(29418), "fake-ssh-key", "gerrit.example.com", "ci", "test-app", testify.Anything). + Return(false, nil) + mock.EXPECT().CreateProject(int32(29418), "fake-ssh-key", "gerrit.example.com", "ci", "test-app", testify.Anything). + Return(errors.New("permission denied")) + return mock }, - wantErr: require.NoError, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, util.ProjectPushedStatus, status.Git) + wantErr: require.Error, + wantCodebaseErrorStatus: func(t *testing.T, codebase *codebaseApi.Codebase) { + require.Equal(t, util.StatusFailed, codebase.Status.Status) + require.Contains(t, codebase.Status.DetailedMessage, "failed to create gerrit project") }, }, { - name: "github, clone strategy - should put project successfully", + name: "failed to push to Gerrit", codebase: &codebaseApi.Codebase{ ObjectMeta: metav1.ObjectMeta{ - Name: "go app", + Name: "test-app", Namespace: defaultNs, }, Spec: codebaseApi.CodebaseSpec{ - Strategy: codebaseApi.Clone, - GitServer: githubGitServer.Name, - GitUrlPath: "/owner/go-repo", - Repository: &codebaseApi.Repository{Url: "https://github.com/owner/repo.git"}, - DefaultBranch: "master", + Strategy: codebaseApi.Create, + GitServer: gerritGitServer.Name, + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: true, }, }, - objects: []client.Object{githubGitServer, githubGitServerSecret}, + objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(cfg gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("GetCurrentBranchName", testify.Anything, testify.Anything). - Return("feature", nil). - On("Checkout", testify.Anything, testify.Anything, testify.Anything, true). - Return(nil). - On("AddRemoteLink", testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("Push", testify.Anything, testify.Anything, testify.Anything). - Return(nil) - + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Commit(testify.Anything, testify.Anything, "Initial commit", testify.Anything).Return(nil) + mock.EXPECT().GetCurrentBranchName(testify.Anything, testify.Anything).Return("main", nil) + mock.EXPECT().AddRemoteLink(testify.Anything, testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Push(testify.Anything, testify.Anything, testify.Anything, testify.Anything). + Return(errors.New("push rejected")) + return func(config gitproviderv2.Config) gitproviderv2.Git { return mock } }, gerritClient: func(t *testing.T) gerrit.Client { - return gerritmocks.NewMockClient(t) - }, - gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { - mock := gitprovidermock.NewMockGitProjectProvider(t) - - mock.On("ProjectExists", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(false, nil). - On("CreateProject", testify.Anything, testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("SetDefaultBranch", testify.Anything, testify.Anything, testify.Anything, testify.Anything, testify.Anything). + mock := gerritMocks.NewMockClient(t) + mock.EXPECT().CheckProjectExist(int32(29418), "fake-ssh-key", "gerrit.example.com", "ci", "test-app", testify.Anything). + Return(false, nil) + mock.EXPECT().CreateProject(int32(29418), "fake-ssh-key", "gerrit.example.com", "ci", "test-app", testify.Anything). Return(nil) - - return func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { - return mock, nil - } - }, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { - return func(config gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil) - - return mock - } + return mock }, - wantErr: require.NoError, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, util.ProjectPushedStatus, status.Git) + wantErr: require.Error, + wantCodebaseErrorStatus: func(t *testing.T, codebase *codebaseApi.Codebase) { + require.Equal(t, util.StatusFailed, codebase.Status.Status) + require.Contains(t, codebase.Status.DetailedMessage, "failed to push changes and tags into git") }, }, { - name: "gitlab, create strategy - should put project successfully", + name: "failed to set HEAD to branch in Gerrit", codebase: &codebaseApi.Codebase{ ObjectMeta: metav1.ObjectMeta{ - Name: "go app", + Name: "test-app", Namespace: defaultNs, }, Spec: codebaseApi.CodebaseSpec{ Strategy: codebaseApi.Create, - GitServer: gitlabGitServer.Name, - GitUrlPath: "/owner/go-repo", - DefaultBranch: "master", + GitServer: gerritGitServer.Name, + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: true, }, }, - objects: []client.Object{gitlabGitServer, gitlabGitServerSecret}, + objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(cfg gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("Init", testify.Anything, testify.Anything). - Return(nil). - On("Commit", testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("GetCurrentBranchName", testify.Anything, testify.Anything). - Return("feature", nil). - On("Checkout", testify.Anything, testify.Anything, testify.Anything, false). - Return(nil). - On("AddRemoteLink", testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("Push", testify.Anything, testify.Anything, testify.Anything). - Return(nil) - + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Commit(testify.Anything, testify.Anything, "Initial commit", testify.Anything).Return(nil) + mock.EXPECT().GetCurrentBranchName(testify.Anything, testify.Anything).Return("main", nil) + mock.EXPECT().AddRemoteLink(testify.Anything, testify.Anything, testify.Anything).Return(nil) + mock.EXPECT().Push(testify.Anything, testify.Anything, testify.Anything, testify.Anything).Return(nil) + return func(config gitproviderv2.Config) gitproviderv2.Git { return mock } }, gerritClient: func(t *testing.T) gerrit.Client { - return gerritmocks.NewMockClient(t) - }, - gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { - mock := gitprovidermock.NewMockGitProjectProvider(t) - - mock.On("ProjectExists", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(false, nil). - On("CreateProject", testify.Anything, testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("SetDefaultBranch", testify.Anything, testify.Anything, testify.Anything, testify.Anything, testify.Anything). + mock := gerritMocks.NewMockClient(t) + mock.EXPECT().CheckProjectExist(int32(29418), "fake-ssh-key", "gerrit.example.com", "ci", "test-app", testify.Anything). + Return(false, nil) + mock.EXPECT().CreateProject(int32(29418), "fake-ssh-key", "gerrit.example.com", "ci", "test-app", testify.Anything). Return(nil) - - return func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { - return mock, nil - } - }, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { - return func(config gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("Init", testify.Anything, testify.Anything). - Return(nil). - On("Commit", testify.Anything, testify.Anything, testify.Anything). - Return(nil) - - return mock - } + mock.EXPECT().SetHeadToBranch(int32(29418), "fake-ssh-key", "gerrit.example.com", "ci", "test-app", "main", testify.Anything). + Return(errors.New("branch does not exist")) + return mock }, - wantErr: require.NoError, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, util.ProjectPushedStatus, status.Git) + wantErr: require.Error, + wantCodebaseErrorStatus: func(t *testing.T, codebase *codebaseApi.Codebase) { + require.Equal(t, util.StatusFailed, codebase.Status.Status) + require.Contains(t, codebase.Status.DetailedMessage, "set remote HEAD for codebase test-app to default branch main has been failed") }, }, { - name: "gitlab, clone strategy - should put project successfully", + name: "failed to clone template repository for non-empty project", codebase: &codebaseApi.Codebase{ ObjectMeta: metav1.ObjectMeta{ - Name: "go app", + Name: "test-app", Namespace: defaultNs, }, Spec: codebaseApi.CodebaseSpec{ - Strategy: codebaseApi.Clone, - GitServer: gitlabGitServer.Name, - GitUrlPath: "/owner/go-repo", - Repository: &codebaseApi.Repository{Url: "https://github.com/owner/repo.git"}, - DefaultBranch: "master", + Strategy: codebaseApi.Create, + GitServer: gerritGitServer.Name, + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: false, + Lang: "go", + BuildTool: "go", + Framework: "beego", }, }, - objects: []client.Object{gitlabGitServer, gitlabGitServerSecret}, + objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(cfg gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("GetCurrentBranchName", testify.Anything, testify.Anything). - Return("feature", nil). - On("Checkout", testify.Anything, testify.Anything, testify.Anything, true). - Return(nil). - On("AddRemoteLink", testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("Push", testify.Anything, testify.Anything, testify.Anything). - Return(nil) - + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Clone(testify.Anything, "https://github.com/epmd-edp/go-go-beego.git", testify.Anything). + Return(errors.New("repository not found")) + return func(config gitproviderv2.Config) gitproviderv2.Git { return mock } }, gerritClient: func(t *testing.T) gerrit.Client { - return gerritmocks.NewMockClient(t) - }, - gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { - mock := gitprovidermock.NewMockGitProjectProvider(t) - - mock.On("ProjectExists", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(false, nil). - On("CreateProject", testify.Anything, testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("SetDefaultBranch", testify.Anything, testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil) - - return func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { - return mock, nil - } - }, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { - return func(config gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil) - - return mock - } + return gerritMocks.NewMockClient(t) }, - wantErr: require.NoError, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, util.ProjectPushedStatus, status.Git) + wantErr: require.Error, + wantCodebaseErrorStatus: func(t *testing.T, codebase *codebaseApi.Codebase) { + require.Equal(t, util.StatusFailed, codebase.Status.Status) + require.Contains(t, codebase.Status.DetailedMessage, "failed to clone template project") }, }, { - name: "gitlab, clone strategy - failed to set default branch", + name: "failed to init empty repository", codebase: &codebaseApi.Codebase{ ObjectMeta: metav1.ObjectMeta{ - Name: "go app", + Name: "test-app", Namespace: defaultNs, }, Spec: codebaseApi.CodebaseSpec{ - Strategy: codebaseApi.Clone, - GitServer: gitlabGitServer.Name, - GitUrlPath: "/owner/go-repo", - Repository: &codebaseApi.Repository{Url: "https://github.com/owner/repo.git"}, - DefaultBranch: "master", + Strategy: codebaseApi.Create, + GitServer: gerritGitServer.Name, + GitUrlPath: "/test-app", + DefaultBranch: "main", + EmptyProject: true, }, }, - objects: []client.Object{gitlabGitServer, gitlabGitServerSecret}, + objects: []client.Object{gerritGitServer, gerritGitServerSecret}, gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return func(cfg gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("GetCurrentBranchName", testify.Anything, testify.Anything). - Return("feature", nil). - On("Checkout", testify.Anything, testify.Anything, testify.Anything, true). - Return(nil). - On("AddRemoteLink", testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("Push", testify.Anything, testify.Anything, testify.Anything). - Return(nil) - - return mock - } - }, - gerritClient: func(t *testing.T) gerrit.Client { - return gerritmocks.NewMockClient(t) - }, - gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { - mock := gitprovidermock.NewMockGitProjectProvider(t) - - mock.On("ProjectExists", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(false, nil). - On("CreateProject", testify.Anything, testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil). - On("SetDefaultBranch", testify.Anything, testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(errors.New("failed to set default branch")) - - return func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { - return mock, nil - } - }, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { + mock := gitmocks.NewMockGit(t) + mock.EXPECT().Init(testify.Anything, testify.Anything).Return(errors.New("failed to initialize git repository")) return func(config gitproviderv2.Config) gitproviderv2.Git { - mock := v2mocks.NewMockGit(t) - - mock.On("CheckPermissions", testify.Anything, testify.Anything). - Return(nil). - On("Clone", testify.Anything, testify.Anything, testify.Anything, testify.Anything). - Return(nil) - return mock } }, - wantErr: func(t require.TestingT, err error, i ...interface{}) { - require.Error(t, err) - assert.Contains(t, err.Error(), "failed to set default branch") - }, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, util.StatusFailed, status.Status) - }, - }, - { - name: "should skip import strategy", - codebase: &codebaseApi.Codebase{ - ObjectMeta: metav1.ObjectMeta{ - Name: "go app", - Namespace: defaultNs, - }, - Spec: codebaseApi.CodebaseSpec{ - Strategy: codebaseApi.Import, - }, - }, - gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return nil - }, - gerritClient: func(t *testing.T) gerrit.Client { - return nil - }, - gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { - return nil - }, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { - return nil - }, - wantErr: require.NoError, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, "", status.Git) - }, - }, - { - name: "should skip if status is already pushed", - codebase: &codebaseApi.Codebase{ - ObjectMeta: metav1.ObjectMeta{ - Name: "go app", - Namespace: defaultNs, - }, - Spec: codebaseApi.CodebaseSpec{ - Strategy: codebaseApi.Create, - }, - Status: codebaseApi.CodebaseStatus{ - Git: util.ProjectPushedStatus, - }, - }, - gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return nil - }, - gerritClient: func(t *testing.T) gerrit.Client { - return nil - }, - gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { - return nil - }, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { - return nil - }, - wantErr: require.NoError, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, util.ProjectPushedStatus, status.Git) - }, - }, - { - name: "should skip if status is template already pushed", - codebase: &codebaseApi.Codebase{ - ObjectMeta: metav1.ObjectMeta{ - Name: "go app", - Namespace: defaultNs, - }, - Spec: codebaseApi.CodebaseSpec{ - Strategy: codebaseApi.Create, - }, - Status: codebaseApi.CodebaseStatus{ - Git: util.ProjectTemplatesPushedStatus, - }, - }, - gitProviderFactory: func(t *testing.T) gitproviderv2.GitProviderFactory { - return nil - }, gerritClient: func(t *testing.T) gerrit.Client { - return nil - }, - gitProvider: func(t *testing.T) func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { - return nil - }, - createGitProviderWithConfig: func(t *testing.T) func(config gitproviderv2.Config) gitproviderv2.Git { - return nil + return gerritMocks.NewMockClient(t) }, - wantErr: require.NoError, - wantStatus: func(t *testing.T, status *codebaseApi.CodebaseStatus) { - assert.Equal(t, util.ProjectTemplatesPushedStatus, status.Git) + wantErr: require.Error, + wantCodebaseErrorStatus: func(t *testing.T, codebase *codebaseApi.Codebase) { + require.Equal(t, util.StatusFailed, codebase.Status.Status) + require.Contains(t, codebase.Status.DetailedMessage, "failed to create empty git repository") }, }, } @@ -1530,7 +1249,9 @@ func TestPutProject_ServeRequest(t *testing.T) { h := NewPutProject( k8sClient, tt.gerritClient(t), - tt.gitProvider(t), + func(gitServer *codebaseApi.GitServer, token string) (gitprovider.GitProjectProvider, error) { + return mocks.NewMockGitProjectProvider(t), nil + }, tt.gitProviderFactory(t), ) @@ -1538,24 +1259,26 @@ func TestPutProject_ServeRequest(t *testing.T) { tt.wantErr(t, err) - if err != nil { - tt.wantStatus(t, &tt.codebase.Status) - return + if tt.wantCodebaseErrorStatus != nil { + tt.wantCodebaseErrorStatus(t, tt.codebase) } processedCodebase := &codebaseApi.Codebase{} - if err = k8sClient.Get( - context.Background(), - types.NamespacedName{ - Name: tt.codebase.Name, - Namespace: tt.codebase.Namespace, - }, - processedCodebase, - ); err != nil { - require.NoError(t, err) - } - tt.wantStatus(t, &processedCodebase.Status) + require.NoError(t, + k8sClient.Get( + context.Background(), + types.NamespacedName{ + Name: tt.codebase.Name, + Namespace: tt.codebase.Namespace, + }, + processedCodebase, + ), + ) + + if tt.wantStatus != nil { + tt.wantStatus(t, processedCodebase.Status) + } }) } } diff --git a/controllers/codebasebranch/chain/check_reference.go b/controllers/codebasebranch/chain/check_reference.go index b5ca71a0..f92d6d17 100644 --- a/controllers/codebasebranch/chain/check_reference.go +++ b/controllers/codebasebranch/chain/check_reference.go @@ -11,7 +11,7 @@ import ( codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1" "github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/chain/handler" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" + gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git" "github.com/epam/edp-codebase-operator/v2/pkg/util" ) diff --git a/controllers/codebasebranch/chain/check_reference_test.go b/controllers/codebasebranch/chain/check_reference_test.go index 77515dce..e444de30 100644 --- a/controllers/codebasebranch/chain/check_reference_test.go +++ b/controllers/codebasebranch/chain/check_reference_test.go @@ -15,8 +15,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" - gitServerMocks "github.com/epam/edp-codebase-operator/v2/pkg/git/v2/mocks" + gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git" + gitServerMocks "github.com/epam/edp-codebase-operator/v2/pkg/git/mocks" ) func TestCheckReferenceExists_ServeRequest(t *testing.T) { diff --git a/controllers/codebasebranch/chain/factory/factory.go b/controllers/codebasebranch/chain/factory/factory.go index bed794c3..a2059cc5 100644 --- a/controllers/codebasebranch/chain/factory/factory.go +++ b/controllers/codebasebranch/chain/factory/factory.go @@ -10,7 +10,7 @@ import ( "github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/chain/put_branch_in_git" "github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/chain/put_codebase_image_stream" "github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/service" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" + gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git" ) func GetDeletionChain() handler.CodebaseBranchHandler { diff --git a/controllers/codebasebranch/chain/put_branch_in_git/put_branch_in_git.go b/controllers/codebasebranch/chain/put_branch_in_git/put_branch_in_git.go index a215cef6..c5e5bbe9 100644 --- a/controllers/codebasebranch/chain/put_branch_in_git/put_branch_in_git.go +++ b/controllers/codebasebranch/chain/put_branch_in_git/put_branch_in_git.go @@ -13,7 +13,7 @@ import ( "github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/chain" "github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/chain/handler" "github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/service" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" + gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git" "github.com/epam/edp-codebase-operator/v2/pkg/model" "github.com/epam/edp-codebase-operator/v2/pkg/util" ) diff --git a/controllers/codebasebranch/chain/put_branch_in_git/put_branch_in_git_test.go b/controllers/codebasebranch/chain/put_branch_in_git/put_branch_in_git_test.go index f6b568f3..a9802fca 100644 --- a/controllers/codebasebranch/chain/put_branch_in_git/put_branch_in_git_test.go +++ b/controllers/codebasebranch/chain/put_branch_in_git/put_branch_in_git_test.go @@ -19,8 +19,8 @@ import ( codebaseApi "github.com/epam/edp-codebase-operator/v2/api/v1" "github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/chain" "github.com/epam/edp-codebase-operator/v2/controllers/codebasebranch/service" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" - gitServerMocks "github.com/epam/edp-codebase-operator/v2/pkg/git/v2/mocks" + gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git" + gitServerMocks "github.com/epam/edp-codebase-operator/v2/pkg/git/mocks" "github.com/epam/edp-codebase-operator/v2/pkg/util" ) diff --git a/deploy-templates/crds/v2.edp.epam.com_gitservers.yaml b/deploy-templates/crds/v2.edp.epam.com_gitservers.yaml index 16bfe100..7fba3638 100644 --- a/deploy-templates/crds/v2.edp.epam.com_gitservers.yaml +++ b/deploy-templates/crds/v2.edp.epam.com_gitservers.yaml @@ -74,6 +74,16 @@ spec: format: int32 type: integer nameSshKeySecret: + description: |- + NameSshKeySecret is the name of the Kubernetes secret containing Git repository credentials. + Required keys: + - token: Git provider access token (required) + Optional keys: + - id_rsa: SSH private key for Git operations over SSH + - secretString: Webhook secret for validating webhook requests + - username: Git username to override the default GitUser + For Gerrit provider, only id_rsa key is required and used. + example: my-git-credentials type: string skipWebhookSSLVerification: description: SkipWebhookSSLVerification is a flag to skip webhook diff --git a/docs/api.md b/docs/api.md index ed508a66..f1b63b13 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1231,7 +1231,14 @@ GitServerSpec defines the desired state of GitServer. nameSshKeySecret string -
+ NameSshKeySecret is the name of the Kubernetes secret containing Git repository credentials. +Required keys: + - token: Git provider access token (required) +Optional keys: + - id_rsa: SSH private key for Git operations over SSH + - secretString: Webhook secret for validating webhook requests + - username: Git username to override the default GitUser +For Gerrit provider, only id_rsa key is required and used.
true diff --git a/pkg/git/v2/factory.go b/pkg/git/factory.go similarity index 100% rename from pkg/git/v2/factory.go rename to pkg/git/factory.go diff --git a/pkg/git/git.go b/pkg/git/git.go index a49d0a23..29be0276 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -1,875 +1,59 @@ -package git +package v2 -import ( - "context" - "errors" - "fmt" - netHttp "net/http" - "net/url" - "os" - "os/exec" - "path" - "strings" - "time" +import "context" - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/config" - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" - "github.com/go-git/go-git/v5/plumbing/storer" - "github.com/go-git/go-git/v5/plumbing/transport" - "github.com/go-git/go-git/v5/plumbing/transport/http" - "github.com/go-git/go-git/v5/storage/memory" - ctrl "sigs.k8s.io/controller-runtime" -) - -const ( - gitCMD = "git" - gitDirName = ".git" - gitDirArg = "--git-dir" - gitBranchArg = "branch" - gitCheckoutArg = "checkout" - getFetchArg = "fetch" - gitOriginArg = "origin" - gitUnshallowArg = "--unshallow" - - gitSshVariantEnv = "GIT_SSH_VARIANT=ssh" -) - -const defaultSshPort = 22 - -const ( - logBranchNameKey = "branchName" - logDirectoryKey = "directory" - logRepositoryKey = "repository" - logOutKey = "out" - - errPlainOpenTmpl = "failed to open git directory %q: %w" - errRemoveSHHKeyFile = "failed to remove key file" -) - -type commitOps struct { - allowEmptyCommit bool -} - -type CommitOps func(*commitOps) - -func CommitAllowEmpty() func(*commitOps) { - return func(o *commitOps) { - o.allowEmptyCommit = true - } -} - -// Git interface provides methods for working with git. -// Deprecated: use pkg/git/v2/provider.go GitProvider instead. +// Git interface provides methods for working with git using v2 GitProvider. +// This interface uses context-aware methods and handles authentication via Config. type Git interface { - CommitChanges(directory, commitMsg string, opts ...CommitOps) error - PushChanges(key, user, directory string, port int32, pushParams ...string) error - CheckPermissions(ctx context.Context, repo string, user, pass *string) (accessible bool) - CloneRepositoryBySsh(ctx context.Context, key, user, repoUrl, destination string, port int32) error - CloneRepository(repo string, user *string, pass *string, destination string) error - CreateRemoteBranch(key, user, path, name, fromcommit string, port int32) error - CreateRemoteTag(key, user, path, branchName, name string) error - Fetch(key, user, path, branchName string) error - Checkout(user, pass *string, directory, branchName string, remote bool) error - GetCurrentBranchName(directory string) (string, error) - Init(directory string) error - CheckoutRemoteBranchBySSH(key, user, gitPath, remoteBranchName string) error - RemoveBranch(directory, branchName string) error - RenameBranch(directory, currentName, newName string) error - CreateChildBranch(directory, currentBranch, newBranch string) error - CommitExists(directory, hash string) (bool, error) - AddRemoteLink(repoPath, remoteUrl string) error - CheckReference(workDir, from string) error -} - -type Command interface { - CombinedOutput() ([]byte, error) -} - -type GitProvider struct { - CommandBuilder func(cmd string, params ...string) Command -} - -func (gp *GitProvider) buildGitCommand(params ...string) Command { - if gp.CommandBuilder == nil { - gp.CommandBuilder = func(cmd string, params ...string) Command { - return exec.Command(cmd, params...) - } - } - - return gp.CommandBuilder(gitCMD, params...) -} - -var log = ctrl.Log.WithName("git-provider") - -func (gp *GitProvider) CreateRemoteBranch(key, user, p, name, from string, port int32) error { - log.Info("start creating remote branch", logBranchNameKey, name) - - r, err := git.PlainOpen(p) - if err != nil { - return fmt.Errorf(errPlainOpenTmpl, p, err) - } - - targetHash, err := resolveReference(r, from) - if err != nil { - return err - } - - branches, err := r.Branches() - if err != nil { - return fmt.Errorf("failed to get branches iterator: %w", err) - } - - exists, err := isBranchExists(name, branches) - if err != nil { - return err - } - - if exists { - log.Info("branch already exists. skip creating", logBranchNameKey, name) - return nil - } - - newRef := plumbing.NewHashReference( - plumbing.NewBranchReferenceName(name), - targetHash, - ) - - err = r.Storer.SetReference(newRef) - if err != nil { - return fmt.Errorf("failed to set reference: %w", err) - } - - err = gp.PushChanges(key, user, p, port, "--all") - if err != nil { - return err - } - - log.Info("branch has been created", logBranchNameKey, name) - - return nil -} - -func (*GitProvider) CommitChanges(directory, commitMsg string, ops ...CommitOps) error { - logger := log.WithValues(logDirectoryKey, directory) - logger.Info("Start committing changes") - - option := &commitOps{ - allowEmptyCommit: false, - } - - for _, applyOption := range ops { - applyOption(option) - } - - r, err := git.PlainOpen(directory) - if err != nil { - return fmt.Errorf(errPlainOpenTmpl, directory, err) - } - - w, err := r.Worktree() - if err != nil { - return fmt.Errorf("failed to get git worktree: %w", err) - } - - _, err = w.Add(".") - if err != nil { - return fmt.Errorf("failed to add files to the index: %w", err) - } - - if !option.allowEmptyCommit { - var status git.Status - - status, err = w.Status() - if err != nil { - return fmt.Errorf("failed to get git status: %w", err) - } - - if status.IsClean() { - logger.Info("Nothing to commit. Skip committing") - - return nil - } - } - - _, err = w.Commit(commitMsg, &git.CommitOptions{ - Author: &object.Signature{ - Name: "codebase", - Email: "codebase@edp.local", - When: time.Now(), - }, - AllowEmptyCommits: option.allowEmptyCommit, - }) - if err != nil { - return fmt.Errorf("failed to commit: %w", err) - } - - logger.Info("Changes have been committed") - - return nil -} - -func (gp *GitProvider) RemoveBranch(directory, branchName string) error { - gitDir := path.Join(directory, gitDirName) - cmd := gp.buildGitCommand(gitDirArg, gitDir, gitBranchArg, "-D", branchName) - - if bts, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("failed to remove branch, err: %s: %w", string(bts), err) - } - - return nil -} - -func (gp *GitProvider) RenameBranch(directory, currentName, newName string) error { - gitDir := path.Join(directory, gitDirName) - cmd := gp.buildGitCommand(gitDirArg, gitDir, gitCheckoutArg, currentName) - - if bts, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("failed to checkout branch, err: %s: %w", string(bts), err) - } - - cmd = gp.buildGitCommand(gitDirArg, gitDir, gitBranchArg, "-m", newName) - if bts, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("failed to rename branch, err: %s: %w", string(bts), err) - } - - return nil -} - -func (gp *GitProvider) CreateChildBranch(directory, currentBranch, newBranch string) error { - gitDir := path.Join(directory, gitDirName) - cmd := gp.buildGitCommand(gitDirArg, gitDir, gitCheckoutArg, currentBranch) - - if bts, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("failed to checkout branch, err: %s: %w", string(bts), err) - } - - cmd = gp.buildGitCommand(gitDirArg, gitDir, gitCheckoutArg, "-b", newBranch) - if bts, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("failed to rename branch, err: %s: %w", string(bts), err) - } - - return nil -} - -func (*GitProvider) PushChanges(key, user, directory string, port int32, pushParams ...string) error { - log.Info("Start pushing changes", logDirectoryKey, directory) - - keyPath, err := InitAuth(key, user) - if err != nil { - return err - } - - defer func() { - if err = os.Remove(keyPath); err != nil { - log.Error(err, errRemoveSHHKeyFile) - } - }() - - gitDir := path.Join(directory, gitDirName) - basePushParams := []string{gitDirArg, gitDir, "push", gitOriginArg} - basePushParams = append(basePushParams, pushParams...) - - pushCMD := exec.Command(gitCMD, basePushParams...) - pushCMD.Env = []string{ - fmt.Sprintf(`GIT_SSH_COMMAND=ssh -i %s -l %s -o StrictHostKeyChecking=no -p %d`, keyPath, - user, port), - gitSshVariantEnv, - } - pushCMD.Dir = directory - - log.Info("pushCMD:", "is: ", basePushParams) - - if bts, err := pushCMD.CombinedOutput(); err != nil { - return fmt.Errorf("failed to push changes, err: %s: %w", string(bts), err) - } - - log.Info("Changes has been pushed", logDirectoryKey, directory) - - return nil -} - -func (*GitProvider) CheckPermissions(ctx context.Context, repo string, user, pass *string) (accessible bool) { - l := ctrl.LoggerFrom(ctx).WithValues(logRepositoryKey, repo) - - l.Info("Checking permissions", "user", user) - - if user == nil || pass == nil { - l.Info("No credentials provided. Skip checking permissions") - return true - } - - r, _ := git.Init(memory.NewStorage(), nil) - remote, _ := r.CreateRemote(&config.RemoteConfig{ - Name: "origin", - URLs: []string{repo}, - }) - - rfs, err := remote.List(&git.ListOptions{ - Auth: &http.BasicAuth{ - Username: *user, - Password: *pass, - }, - }) - if err != nil { - if errors.Is(err, transport.ErrEmptyRemoteRepository) { - l.Error(err, "No refs in repository") - return false - } - - l.Error(err, fmt.Sprintf("User %v do not have access to %v repository", user, repo)) - - return false - } - - if len(rfs) == 0 { - l.Error(errors.New("there are not refs in repository"), "No refs in repository") - return false - } - - return true -} - -func (*GitProvider) BareToNormal(p string) error { - const readWriteExecutePermBits = 0o777 - - gitDir := path.Join(p, gitDirName) - - if err := os.MkdirAll(gitDir, readWriteExecutePermBits); err != nil { - return fmt.Errorf("failed to create .git folder: %w", err) - } - - files, err := os.ReadDir(p) - if err != nil { - return fmt.Errorf("failed to list dir: %w", err) - } - - for _, f := range files { - if f.Name() == gitDirName { - continue - } - - oldPath := path.Join(p, f.Name()) - newPath := path.Join(p, gitDirName, f.Name()) - - if err := os.Rename(oldPath, newPath); err != nil { - return fmt.Errorf("failed to rename file: %w", err) - } - } - - cmd := exec.Command(gitCMD, gitDirArg, gitDir, "config", "--local", - "--bool", "core.bare", "false") - if bts, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("%s: %w", string(bts), err) - } - - cmd = exec.Command(gitCMD, gitDirArg, gitDir, "config", "--local", - "--bool", "remote.origin.mirror", "false") - if bts, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("%s: %w", string(bts), err) - } - - cmd = exec.Command(gitCMD, gitDirArg, gitDir, "reset", "--hard") - cmd.Dir = p - - if bts, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("%s: %w", string(bts), err) - } - - return nil -} - -func (gp *GitProvider) CloneRepositoryBySsh(ctx context.Context, key, user, repoUrl, destination string, port int32) error { - l := ctrl.LoggerFrom(ctx) - - l.Info("Start cloning", logRepositoryKey, repoUrl) - - keyPath, err := InitAuth(key, user) - if err != nil { - return err - } - - defer func() { - if err = os.Remove(keyPath); err != nil { - l.Error(err, errRemoveSHHKeyFile) - } - }() - - cloneCMD := exec.Command(gitCMD, "clone", "--mirror", "--depth", "1", repoUrl, destination) - cloneCMD.Env = []string{fmt.Sprintf(`GIT_SSH_COMMAND=ssh -i %s -l %s -o StrictHostKeyChecking=no -p %d`, - keyPath, user, port), gitSshVariantEnv} - - bytes, err := cloneCMD.CombinedOutput() - if err != nil { - return fmt.Errorf("failed to clone repo by ssh, err: %s: %w", string(bytes), err) - } - - fetchCMD := exec.Command(gitCMD, gitDirArg, destination, getFetchArg, gitUnshallowArg) - fetchCMD.Env = cloneCMD.Env - - bts, err := fetchCMD.CombinedOutput() - if err != nil { - return fmt.Errorf("failed to fetch unshallow repo: %s: %w", string(bts), err) - } - - l.Info("Result of `git fetch unshallow` command", logOutKey, string(bts)) - - err = gp.BareToNormal(destination) - if err != nil { - return fmt.Errorf("failed to covert bare repo to normal: %w", err) - } + // Clone clones a repository to the specified destination with full history. + Clone(ctx context.Context, repoURL, destination string) error - l.Info("End cloning", logRepositoryKey, repoUrl) + // Commit commits changes in the working directory. + Commit(ctx context.Context, directory, message string, ops ...CommitOps) error - return nil -} - -func (gp *GitProvider) CloneRepository(repo string, user, pass *string, destination string) error { - log.Info("Start cloning", logRepositoryKey, repo) - - const httpClientErrors = 400 - - if user != nil && pass != nil { - u, err := url.Parse(repo) - if err != nil { - return fmt.Errorf("failed to parse repo url: %w", err) - } - - u.User = url.UserPassword(*user, *pass) - repo = u.String() - } else { - rsp, err := netHttp.Get(repo) - if err != nil { - return fmt.Errorf("failed to get repo: %w", err) - } - - if rsp.StatusCode >= httpClientErrors { - return fmt.Errorf("repo access denied, response code: %d: %w", rsp.StatusCode, err) - } - } - - cloneCMD := exec.Command(gitCMD, "clone", "--mirror", "--depth", "1", repo, destination) - - if bts, err := cloneCMD.CombinedOutput(); err != nil { - return fmt.Errorf("failed to clone repo: %s: %w", string(bts), err) - } - - fetchCMD := exec.Command(gitCMD, gitDirArg, destination, getFetchArg, gitUnshallowArg) - - bts, err := fetchCMD.CombinedOutput() - if err != nil { - return fmt.Errorf("failed to fetch unshallow repo: %s: %w", string(bts), err) - } - - log.Info("Result of `git fetch unshallow` command", logOutKey, string(bts)) - - err = gp.BareToNormal(destination) - if err != nil { - return fmt.Errorf("failed to covert bare repo to normal: %w", err) - } - - log.Info("End cloning", logRepositoryKey, repo) - - return nil -} - -func (gp *GitProvider) CreateRemoteTag(key, user, p, branchName, name string) error { - log.Info("start creating remote tag", "tagName", name) - - r, err := git.PlainOpen(p) - if err != nil { - return fmt.Errorf(errPlainOpenTmpl, p, err) - } - - tags, err := r.Tags() - if err != nil { - return fmt.Errorf("failed to get git tags: %w", err) - } - - exists, err := isTagExists(name, tags) - if err != nil { - return err - } - - if exists { - log.Info("tag already exists. skip creating", "tagName", name) - return nil - } - - ref, err := r.Reference(plumbing.ReferenceName(fmt.Sprintf("refs/heads/%v", branchName)), false) - if err != nil { - return fmt.Errorf("failed to get reference: %w", err) - } - - newRef := plumbing.NewReferenceFromStrings(fmt.Sprintf("refs/tags/%v", name), ref.Hash().String()) - - err = r.Storer.SetReference(newRef) - if err != nil { - return fmt.Errorf("failed to set reference: %w", err) - } - - err = gp.PushChanges(key, user, p, defaultSshPort) - if err != nil { - return err - } - - log.Info("tag has been created", "tagName", name) - - return nil -} - -func (*GitProvider) Fetch(key, user, workDir, branchName string) error { - log.Info("start fetching data", logBranchNameKey, branchName) - - keyPath, err := InitAuth(key, user) - if err != nil { - return err - } - - defer func() { - if err = os.Remove(keyPath); err != nil { - log.Error(err, errRemoveSHHKeyFile) - } - }() - - gitDir := path.Join(workDir, gitDirName) - cmd := exec.Command(gitCMD, gitDirArg, gitDir, getFetchArg, fmt.Sprintf("refs/heads/%v:refs/heads/%v", branchName, branchName)) - cmd.Env = []string{ - fmt.Sprintf(`GIT_SSH_COMMAND=ssh -i %s -l %s -o StrictHostKeyChecking=no`, keyPath, user), - gitSshVariantEnv, - } - cmd.Dir = workDir - - if bts, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("failed to push changes, err: %s: %w", string(bts), err) - } - - log.Info("end fetching data", logBranchNameKey, branchName) - - return nil -} - -func (*GitProvider) Checkout(user, pass *string, directory, branchName string, remote bool) error { - log.Info("trying to checkout to branch", logBranchNameKey, branchName) - - r, err := git.PlainOpen(directory) - if err != nil { - return fmt.Errorf(errPlainOpenTmpl, directory, err) - } - - w, err := r.Worktree() - if err != nil { - return fmt.Errorf("failed to get git worktree: %w", err) - } - - createBranchOrNot := true + // Push pushes changes to the remote repository. + // refspecs: optional refspecs (e.g., RefSpecPushAllBranches, RefSpecPushAllTags). + Push(ctx context.Context, directory string, refspecs ...string) error - if remote { - gfo := &git.FetchOptions{RefSpecs: []config.RefSpec{"refs/*:refs/*"}} - if user != nil && pass != nil { - gfo = &git.FetchOptions{ - RefSpecs: []config.RefSpec{"refs/*:refs/*"}, - Auth: &http.BasicAuth{ - Username: *user, - Password: *pass, - }, - } - } + // Checkout checks out a branch in the repository. + // If remote is true, fetches from remote first and only creates local branch if it doesn't exist remotely. + Checkout(ctx context.Context, directory, branchName string, remote bool) error - err = r.Fetch(gfo) - if err != nil { - if err.Error() != "already up-to-date" { - return fmt.Errorf("failed to fetch: %w", err) - } - } + // CreateRemoteBranch creates a new branch from a reference and pushes it to remote. + // fromRef: branch name or commit hash to create from (empty string means HEAD). + CreateRemoteBranch(ctx context.Context, directory, branchName, fromRef string) error - createBranchOrNot, err = checkBranchExistence(user, pass, branchName, *r) - if err != nil { - return err - } - } + // GetCurrentBranchName returns the name of the current branch. + GetCurrentBranchName(ctx context.Context, directory string) (string, error) - err = w.Checkout(&git.CheckoutOptions{ - Branch: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branchName)), - Force: true, - Create: createBranchOrNot, - }) - if err != nil { - return fmt.Errorf("failed to checkout git branch: %w", err) - } - - return nil -} - -func (*GitProvider) GetCurrentBranchName(directory string) (string, error) { - log.Info("trying to get current git branch") - - r, err := git.PlainOpen(directory) - if err != nil { - return "", fmt.Errorf(errPlainOpenTmpl, directory, err) - } - - ref, err := r.Head() - if err != nil { - return "", fmt.Errorf("failed to get HEAD reference: %w", err) - } - - branchName := strings.ReplaceAll(ref.Name().String(), "refs/heads/", "") - - return branchName, nil -} - -func (*GitProvider) Init(directory string) error { - log.Info("start creating git repository") - - _, err := git.PlainInit(directory, false) - if err != nil { - return fmt.Errorf("failed to init Git repository: %w", err) - } - - log.Info("git repository has been created") - - return nil -} + // CheckPermissions checks if the repository is accessible with current credentials. + CheckPermissions(ctx context.Context, repoURL string) error -func (*GitProvider) CheckoutRemoteBranchBySSH(key, user, gitPath, remoteBranchName string) error { - log.Info("start checkout to", "branch", remoteBranchName) + // CheckReference checks if a reference (branch or commit) exists in the repository. + CheckReference(ctx context.Context, directory, refName string) error - keyPath, err := InitAuth(key, user) - if err != nil { - return err - } + // RemoveBranch removes a local branch. + RemoveBranch(ctx context.Context, directory, branchName string) error - defer func() { - if err = os.Remove(keyPath); err != nil { - log.Error(err, errRemoveSHHKeyFile) - } - }() - - gitDir := path.Join(gitPath, ".git") - - // running git fetch --update-head-ok - cmdFetch := exec.Command(gitCMD, gitDirArg, gitDir, getFetchArg, "--update-head-ok") - cmdFetch.Env = []string{ - fmt.Sprintf(`GIT_SSH_COMMAND=ssh -i %s -l %s -o StrictHostKeyChecking=no`, keyPath, user), - gitSshVariantEnv, - } - cmdFetch.Dir = gitPath - - if bts, err := cmdFetch.CombinedOutput(); err != nil { - return fmt.Errorf("failed to fetch branches, err: %s: %w", string(bts), err) - } - - // here we expect that remote branch exists otherwise return error - // git checkout -b remoteBranchName remoteBranchName - cmdCheckout := exec.Command(gitCMD, gitDirArg, gitDir, gitCheckoutArg, remoteBranchName) - cmdCheckout.Dir = gitPath - - if bts, err := cmdCheckout.CombinedOutput(); err != nil { - return fmt.Errorf("failed to checkout to branch, err: %s: %w", string(bts), err) - } - - log.Info("end checkout to", "branch", remoteBranchName) - - return nil -} - -// CommitExists checks if a commit exists in the repository. -func (*GitProvider) CommitExists(directory, hash string) (bool, error) { - r, err := git.PlainOpen(directory) - if err != nil { - return false, fmt.Errorf(errPlainOpenTmpl, directory, err) - } - - commit, err := r.CommitObject(plumbing.NewHash(hash)) - if err != nil { - if errors.Is(err, plumbing.ErrObjectNotFound) { - return false, nil - } - - return false, fmt.Errorf("failed to get commit object: %w", err) - } - - return commit != nil, nil -} - -// AddRemoteLink adds a remote link to the repository. -func (*GitProvider) AddRemoteLink(repoPath, remoteUrl string) error { - r, err := git.PlainOpen(repoPath) - if err != nil { - return fmt.Errorf("failed to open Git directory: %w", err) - } - - err = r.DeleteRemote("origin") - if err != nil && !errors.Is(err, git.ErrRemoteNotFound) { - return fmt.Errorf("failed to delete remote origin: %w", err) - } - - _, err = r.CreateRemote(&config.RemoteConfig{ - Name: "origin", - URLs: []string{remoteUrl}, - }) - if err != nil { - return fmt.Errorf("failed to create remote origin: %w", err) - } - - return nil -} - -// CheckReference checks if a reference (branch or commit) exists in the repository. -func (gp *GitProvider) CheckReference(workDir, from string) error { - if from == "" { - return nil - } - - r, err := git.PlainOpen(workDir) - if err != nil { - return fmt.Errorf("failed to open git repository: %w", err) - } - - _, err = resolveReference(r, from) - - return err -} - -func isBranchExists(name string, branches storer.ReferenceIter) (bool, error) { - for { - b, err := branches.Next() - if err != nil { - if err.Error() == "EOF" { - return false, nil - } - - return false, fmt.Errorf("failed to get next branch iterator: %w", err) - } - - if b.Name().Short() == name { - return true, nil - } - } -} - -func InitAuth(key, user string) (string, error) { - log.Info("Initializing auth", "user", user) - - keyFile, err := os.CreateTemp("", "sshkey") - if err != nil { - return "", fmt.Errorf("failed to create temp file for ssh key: %w", err) - } - - keyFileInfo, _ := keyFile.Stat() - keyFilePath := path.Join(os.TempDir(), keyFileInfo.Name()) - - // write the key to the file with a new line at the end to avoid ssh errors on git commands - // if the new line already exists, adding the new line will not cause any issues - if _, err = fmt.Fprintf(keyFile, "%s%s", key, "\n"); err != nil { - return "", fmt.Errorf("failed to write ssh key: %w", err) - } - - if err = keyFile.Close(); err != nil { - return "", fmt.Errorf("failed to close file: %w", err) - } - - const readOnlyPermBits = 0o400 - - if err := os.Chmod(keyFilePath, readOnlyPermBits); err != nil { - return "", fmt.Errorf("failed to chmod ssh key file: %w", err) - } - - return keyFilePath, nil -} - -func isTagExists(name string, tags storer.ReferenceIter) (bool, error) { - for { - t, err := tags.Next() - if err != nil { - if err.Error() == "EOF" { - return false, nil - } - - return false, fmt.Errorf("failed to get next reference iterator: %w", err) - } - - if t.Name().Short() == name { - return true, nil - } - } -} - -func checkBranchExistence(user, pass *string, branchName string, r git.Repository) (bool, error) { - log.Info("checking if branch exist", logBranchNameKey, branchName) - - remote, err := r.Remote("origin") - if err != nil { - return false, fmt.Errorf("failed to get GIT remove origin: %w", err) - } - - glo := &git.ListOptions{} - - if user != nil && pass != nil { - glo = &git.ListOptions{ - Auth: &http.BasicAuth{ - Username: *user, - Password: *pass, - }, - } - } - - refList, err := remote.List(glo) - if err != nil { - return false, fmt.Errorf("failed to get references on the remote repository: %w", err) - } - - existBranchOrNot := true - refPrefix := "refs/heads/" - - for _, ref := range refList { - refName := ref.Name().String() - if !strings.HasPrefix(refName, refPrefix) { - continue - } - - b := refName[len(refPrefix):] - if b == branchName { - existBranchOrNot = false - break - } - } - - log.Info("branch existence status", logBranchNameKey, branchName, "existBranchOrNot", existBranchOrNot) - - return existBranchOrNot, nil -} + // CreateChildBranch creates a new branch from an existing branch. + CreateChildBranch(ctx context.Context, directory, parentBranch, newBranch string) error -// resolveReference resolves a reference (branch or commit) to a hash. -func resolveReference(r *git.Repository, from string) (plumbing.Hash, error) { - if from == "" { - // If no reference specified, use HEAD - ref, err := r.Head() - if err != nil { - return plumbing.ZeroHash, fmt.Errorf("failed to get git HEAD reference: %w", err) - } + // Init initializes a new git repository. + Init(ctx context.Context, directory string) error - return ref.Hash(), nil - } + // Fetch fetches changes from the remote repository. + // branchName: specific branch to fetch (empty string fetches all). + Fetch(ctx context.Context, directory, branchName string) error - // Try to resolve as a branch first - branchRef, err := r.Reference(plumbing.NewBranchReferenceName(from), false) - if err == nil { - return branchRef.Hash(), nil - } + // AddRemoteLink adds or updates the remote origin URL. + AddRemoteLink(ctx context.Context, directory, remoteURL string) error - // If not a branch, try to resolve as a commit - commitHash := plumbing.NewHash(from) - if commitHash.IsZero() { - return plumbing.ZeroHash, fmt.Errorf("invalid reference or commit hash: %s", from) - } + // CommitExists checks if a commit with the given hash exists in the repository. + CommitExists(ctx context.Context, directory, hash string) (bool, error) - _, err = r.CommitObject(commitHash) - if err != nil { - return plumbing.ZeroHash, fmt.Errorf("failed to get commit %s: %w", from, err) - } + // CheckoutRemoteBranch fetches from remote and checks out the specified branch. + CheckoutRemoteBranch(ctx context.Context, directory, branchName string) error - return commitHash, nil + // CreateRemoteTag creates a tag from a branch and pushes it to the remote repository. + CreateRemoteTag(ctx context.Context, directory, branchName, tagName string) error } diff --git a/pkg/git/git_test.go b/pkg/git/git_test.go deleted file mode 100644 index b3ec8af2..00000000 --- a/pkg/git/git_test.go +++ /dev/null @@ -1,577 +0,0 @@ -package git_test - -import ( - "context" - "encoding/base64" - "net/http" - "net/http/httptest" - "os" - "path" - "testing" - "time" - - gogit "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - ctrl "sigs.k8s.io/controller-runtime" - - "github.com/epam/edp-codebase-operator/v2/pkg/git" - gitproviderv2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" - "github.com/epam/edp-codebase-operator/v2/pkg/platform" -) - -func TestGitProvider_CheckPermissions(t *testing.T) { - user := "user" - pass := "pass" - - config := gitproviderv2.Config{ - Username: user, - Token: pass, - } - gp := gitproviderv2.NewGitProvider(config) - - bts, err := base64.StdEncoding.DecodeString(`MDAxZSMgc2VydmljZT1naXQtdXBsb2FkLXBhY2sKMDAwMDAxNTY2ZWNmMGVmMmMyZGZmYjc5NjAzM2U1YTAyMjE5YWY4NmVjNjU4NGU1IEhFQUQAbXVsdGlfYWNrIHRoaW4tcGFjayBzaWRlLWJhbmQgc2lkZS1iYW5kLTY0ayBvZnMtZGVsdGEgc2hhbGxvdyBkZWVwZW4tc2luY2UgZGVlcGVuLW5vdCBkZWVwZW4tcmVsYXRpdmUgbm8tcHJvZ3Jlc3MgaW5jbHVkZS10YWcgbXVsdGlfYWNrX2RldGFpbGVkIGFsbG93LXRpcC1zaGExLWluLXdhbnQgYWxsb3ctcmVhY2hhYmxlLXNoYTEtaW4td2FudCBuby1kb25lIHN5bXJlZj1IRUFEOnJlZnMvaGVhZHMvbWFzdGVyIGZpbHRlciBvYmplY3QtZm9ybWF0PXNoYTEgYWdlbnQ9Z2l0L2dpdGh1Yi1nNzhiNDUyNDEzZThiCjAwM2ZlOGQzZmZhYjU1Mjg5NWMxOWI5ZmNmN2FhMjY0ZDI3N2NkZTMzODgxIHJlZnMvaGVhZHMvYnJhbmNoCjAwM2Y2ZWNmMGVmMmMyZGZmYjc5NjAzM2U1YTAyMjE5YWY4NmVjNjU4NGU1IHJlZnMvaGVhZHMvbWFzdGVyCjAwM2ViOGU0NzFmNThiY2JjYTYzYjA3YmRhMjBlNDI4MTkwNDA5YzJkYjQ3IHJlZnMvcHVsbC8xL2hlYWQKMDAzZTk2MzJmMDI4MzNiMmY5NjEzYWZiNWU3NTY4MjEzMmIwYjIyZTRhMzEgcmVmcy9wdWxsLzIvaGVhZAowMDNmYzM3ZjU4YTEzMGNhNTU1ZTQyZmY5NmEwNzFjYjljY2IzZjQzNzUwNCByZWZzL3B1bGwvMi9tZXJnZQowMDAw`) - require.NoError(t, err) - - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - _, err = w.Write(bts) - assert.NoError(t, err, "failed to write response") - })) - defer s.Close() - - err = gp.CheckPermissions(context.Background(), s.URL) - require.NoError(t, err, "repo must be accessible") -} - -func TestGitProvider_CheckPermissions_NoRefs(t *testing.T) { - user := "user" - pass := "pass" - - config := gitproviderv2.Config{ - Username: user, - Token: pass, - } - gp := gitproviderv2.NewGitProvider(config) - - bts, err := base64.StdEncoding.DecodeString(`MDAxZSMgc2VydmljZT1naXQtdXBsb2FkLXBhY2sKMDAwMDAwZGUwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIGNhcGFiaWxpdGllc157fQAgaW5jbHVkZS10YWcgbXVsdGlfYWNrX2RldGFpbGVkIG11bHRpX2FjayBvZnMtZGVsdGEgc2lkZS1iYW5kIHNpZGUtYmFuZC02NGsgdGhpbi1wYWNrIG5vLXByb2dyZXNzIHNoYWxsb3cgbm8tZG9uZSBhZ2VudD1KR2l0L3Y1LjkuMC4yMDIwMDkwODA1MDEtci00MS1nNWQ5MjVlY2JiCjAwMDA=`) - require.NoError(t, err) - - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - _, err = w.Write(bts) - assert.NoError(t, err, "failed to write response") - })) - defer s.Close() - - mockLogger := platform.NewLoggerMock() - - // v2 implementation returns nil for empty repos (they are technically accessible, just empty) - // This is different from v1 which logged an error - err = gp.CheckPermissions(ctrl.LoggerInto(context.Background(), mockLogger), s.URL) - require.NoError(t, err, "v2 considers empty repos accessible") -} - -func TestInitAuth(t *testing.T) { - dir, err := git.InitAuth("foo", "bar") - assert.NoError(t, err) - assert.Contains(t, dir, "sshkey") -} - -func TestGitProvider_CreateChildBranch(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - initRepo func(t *testing.T) string - parent string - child string - wantErr require.ErrorAssertionFunc - }{ - { - name: "should create child branch successfully", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - r, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - // Create initial commit on master branch - w, err := r.Worktree() - require.NoError(t, err) - - f, err := os.Create(path.Join(dir, "test.txt")) - require.NoError(t, err) - _, err = f.WriteString("test content") - require.NoError(t, err) - require.NoError(t, f.Close()) - - _, err = w.Add("test.txt") - require.NoError(t, err) - - _, err = w.Commit("initial commit", &gogit.CommitOptions{ - Author: &object.Signature{ - Name: "test", - Email: "test@example.com", - When: time.Now(), - }, - }) - require.NoError(t, err) - - // Create a parent branch and check it out so it exists as a proper reference - err = w.Checkout(&gogit.CheckoutOptions{ - Branch: plumbing.NewBranchReferenceName("parent-branch"), - Create: true, - }) - require.NoError(t, err) - - return dir - }, - parent: "parent-branch", - child: "child-branch", - wantErr: require.NoError, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - gp := gitproviderv2.NewGitProvider(gitproviderv2.Config{}) - dir := tt.initRepo(t) - - err := gp.CreateChildBranch(context.Background(), dir, tt.parent, tt.child) - tt.wantErr(t, err) - }) - } -} - -func TestGitProvider_RemoveBranch(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - initRepo func(t *testing.T) string - branch string - wantErr require.ErrorAssertionFunc - }{ - { - name: "should remove branch successfully", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - r, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - // Create initial commit - w, err := r.Worktree() - require.NoError(t, err) - - f, err := os.Create(path.Join(dir, "test.txt")) - require.NoError(t, err) - _, err = f.WriteString("test content") - require.NoError(t, err) - require.NoError(t, f.Close()) - - _, err = w.Add("test.txt") - require.NoError(t, err) - - _, err = w.Commit("initial commit", &gogit.CommitOptions{ - Author: &object.Signature{ - Name: "test", - Email: "test@example.com", - When: time.Now(), - }, - }) - require.NoError(t, err) - - // Create a new branch - err = w.Checkout(&gogit.CheckoutOptions{ - Branch: plumbing.NewBranchReferenceName("test-branch"), - Create: true, - }) - require.NoError(t, err) - - // Checkout back to master so we can delete test-branch - err = w.Checkout(&gogit.CheckoutOptions{ - Branch: plumbing.NewBranchReferenceName("master"), - }) - require.NoError(t, err) - - return dir - }, - branch: "test-branch", - wantErr: require.NoError, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - gp := gitproviderv2.NewGitProvider(gitproviderv2.Config{}) - dir := tt.initRepo(t) - - err := gp.RemoveBranch(context.Background(), dir, tt.branch) - tt.wantErr(t, err) - }) - } -} - -func Test_initAuth(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - key string - want string - wantErr require.ErrorAssertionFunc - }{ - { - name: "success without empty line in the end", - key: `-----KEY----- -some-key ------END-----`, - want: `-----KEY----- -some-key ------END----- -`, - wantErr: require.NoError, - }, - { - name: "success with empty line in the end", - key: `-----KEY----- -some-key ------END----- -`, - want: `-----KEY----- -some-key ------END----- - -`, - wantErr: require.NoError, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - got, err := git.InitAuth(tt.key, "user") - tt.wantErr(t, err) - - gotKey, err := os.ReadFile(got) - require.NoError(t, err) - assert.Equal(t, tt.want, string(gotKey)) - }) - } -} - -func TestGitProvider_CommitChanges(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - ops []gitproviderv2.CommitOps - initRepo func(t *testing.T) string - wantErr require.ErrorAssertionFunc - checkRepo func(t *testing.T, dir string) - }{ - { - name: "should commit changes successfully", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - _, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - _, err = os.Create(path.Join(dir, "config.yaml")) - require.NoError(t, err) - - return dir - }, - wantErr: require.NoError, - checkRepo: func(t *testing.T, dir string) { - r, err := gogit.PlainOpen(dir) - require.NoError(t, err) - - commits, err := r.CommitObjects() - require.NoError(t, err) - - count := 0 - _ = commits.ForEach(func(*object.Commit) error { - count++ - - return nil - }) - - require.Equalf(t, 1, count, "expected 1 commits, got %d", count) - }, - }, - { - name: "skip commit if no changes", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - _, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - return dir - }, - wantErr: require.NoError, - checkRepo: func(t *testing.T, dir string) { - r, err := gogit.PlainOpen(dir) - require.NoError(t, err) - - commits, err := r.CommitObjects() - require.NoError(t, err) - - count := 0 - _ = commits.ForEach(func(*object.Commit) error { - count++ - - return nil - }) - - require.Equalf(t, 0, count, "expected 0 commits, got %d", count) - }, - }, - { - name: "should create empty commit", - ops: []gitproviderv2.CommitOps{ - gitproviderv2.CommitAllowEmpty(), - }, - initRepo: func(t *testing.T) string { - dir := t.TempDir() - _, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - return dir - }, - wantErr: require.NoError, - checkRepo: func(t *testing.T, dir string) { - r, err := gogit.PlainOpen(dir) - require.NoError(t, err) - - commits, err := r.CommitObjects() - require.NoError(t, err) - - count := 0 - _ = commits.ForEach(func(*object.Commit) error { - count++ - - return nil - }) - - require.Equalf(t, 1, count, "expected 1 commits, got %d", count) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - gp := gitproviderv2.NewGitProvider(gitproviderv2.Config{}) - dir := tt.initRepo(t) - - err := gp.Commit(context.Background(), dir, "test commit message", tt.ops...) - tt.wantErr(t, err) - tt.checkRepo(t, dir) - }) - } -} - -func TestGitProvider_AddRemoteLink(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - remoteUrl string - initRepo func(t *testing.T) string - wantErr require.ErrorAssertionFunc - checkRepo func(t *testing.T, dir string) - }{ - { - name: "should add remote link successfully", - remoteUrl: "git@host:32/app.git", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - _, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - return dir - }, - wantErr: require.NoError, - checkRepo: func(t *testing.T, dir string) { - r, err := gogit.PlainOpen(dir) - require.NoError(t, err) - - remote, err := r.Remote("origin") - require.NoError(t, err) - - require.Equal(t, "origin", remote.Config().Name) - require.Len(t, remote.Config().URLs, 1) - require.Equal(t, "git@host:32/app.git", remote.Config().URLs[0]) - }, - }, - { - name: "empty git dir", - remoteUrl: "git@host:32/app.git", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - - return dir - }, - wantErr: require.Error, - checkRepo: func(t *testing.T, dir string) {}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - gp := gitproviderv2.NewGitProvider(gitproviderv2.Config{}) - dir := tt.initRepo(t) - - err := gp.AddRemoteLink(context.Background(), dir, tt.remoteUrl) - tt.wantErr(t, err) - tt.checkRepo(t, dir) - }) - } -} - -func TestGitProvider_CheckReference(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - initRepo func(t *testing.T) string - from string - wantErr require.ErrorAssertionFunc - }{ - { - name: "should return nil for empty reference", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - _, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - return dir - }, - from: "", - wantErr: require.NoError, - }, - { - name: "should find existing branch reference", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - r, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - // Create initial commit - w, err := r.Worktree() - require.NoError(t, err) - - f, err := os.Create(path.Join(dir, "test.txt")) - require.NoError(t, err) - _, err = f.WriteString("test content") - require.NoError(t, err) - require.NoError(t, f.Close()) - - _, err = w.Add("test.txt") - require.NoError(t, err) - - _, err = w.Commit("initial commit", &gogit.CommitOptions{ - Author: &object.Signature{ - Name: "test", - Email: "test@example.com", - When: time.Now(), - }, - }) - require.NoError(t, err) - - // Create and checkout a new branch - err = w.Checkout(&gogit.CheckoutOptions{ - Branch: plumbing.NewBranchReferenceName("test-branch"), - Create: true, - }) - require.NoError(t, err) - - return dir - }, - from: "test-branch", - wantErr: require.NoError, - }, - { - name: "should find existing commit reference", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - r, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - // Create initial commit - w, err := r.Worktree() - require.NoError(t, err) - - f, err := os.Create(path.Join(dir, "test.txt")) - require.NoError(t, err) - _, err = f.WriteString("test content") - require.NoError(t, err) - require.NoError(t, f.Close()) - - _, err = w.Add("test.txt") - require.NoError(t, err) - - commit, err := w.Commit("initial commit", &gogit.CommitOptions{ - Author: &object.Signature{ - Name: "test", - Email: "test@example.com", - When: time.Now(), - }, - }) - require.NoError(t, err) - - // Store the commit hash for the test - t.Logf("Created commit with hash: %s", commit.String()) - - return dir - }, - from: "", // Will be set dynamically - wantErr: require.NoError, - }, - { - name: "should return error for non-existent reference", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - _, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - return dir - }, - from: "non-existent", - wantErr: require.Error, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - gp := gitproviderv2.NewGitProvider(gitproviderv2.Config{}) - dir := tt.initRepo(t) - - // For the commit reference test, we need to get the actual commit hash - if tt.name == "should find existing commit reference" { - r, err := gogit.PlainOpen(dir) - require.NoError(t, err) - - ref, err := r.Head() - require.NoError(t, err) - - tt.from = ref.Hash().String() - t.Logf("Using commit hash: %s", tt.from) - } - - err := gp.CheckReference(context.Background(), dir, tt.from) - tt.wantErr(t, err) - }) - } -} diff --git a/pkg/git/mocks/command_mock.go b/pkg/git/mocks/command_mock.go deleted file mode 100644 index baaf7dfb..00000000 --- a/pkg/git/mocks/command_mock.go +++ /dev/null @@ -1,89 +0,0 @@ -// Code generated by mockery. DO NOT EDIT. - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// MockCommand is an autogenerated mock type for the Command type -type MockCommand struct { - mock.Mock -} - -type MockCommand_Expecter struct { - mock *mock.Mock -} - -func (_m *MockCommand) EXPECT() *MockCommand_Expecter { - return &MockCommand_Expecter{mock: &_m.Mock} -} - -// CombinedOutput provides a mock function with no fields -func (_m *MockCommand) CombinedOutput() ([]byte, error) { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for CombinedOutput") - } - - var r0 []byte - var r1 error - if rf, ok := ret.Get(0).(func() ([]byte, error)); ok { - return rf() - } - if rf, ok := ret.Get(0).(func() []byte); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]byte) - } - } - - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockCommand_CombinedOutput_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CombinedOutput' -type MockCommand_CombinedOutput_Call struct { - *mock.Call -} - -// CombinedOutput is a helper method to define mock.On call -func (_e *MockCommand_Expecter) CombinedOutput() *MockCommand_CombinedOutput_Call { - return &MockCommand_CombinedOutput_Call{Call: _e.mock.On("CombinedOutput")} -} - -func (_c *MockCommand_CombinedOutput_Call) Run(run func()) *MockCommand_CombinedOutput_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockCommand_CombinedOutput_Call) Return(_a0 []byte, _a1 error) *MockCommand_CombinedOutput_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockCommand_CombinedOutput_Call) RunAndReturn(run func() ([]byte, error)) *MockCommand_CombinedOutput_Call { - _c.Call.Return(run) - return _c -} - -// NewMockCommand creates a new instance of MockCommand. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockCommand(t interface { - mock.TestingT - Cleanup(func()) -}) *MockCommand { - mock := &MockCommand{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/git/mocks/git_mock.go b/pkg/git/mocks/git_mock.go index f9399b79..ba7f78bc 100644 --- a/pkg/git/mocks/git_mock.go +++ b/pkg/git/mocks/git_mock.go @@ -5,7 +5,7 @@ package mocks import ( context "context" - git "github.com/epam/edp-codebase-operator/v2/pkg/git" + v2 "github.com/epam/edp-codebase-operator/v2/pkg/git" mock "github.com/stretchr/testify/mock" ) @@ -22,17 +22,17 @@ func (_m *MockGit) EXPECT() *MockGit_Expecter { return &MockGit_Expecter{mock: &_m.Mock} } -// AddRemoteLink provides a mock function with given fields: repoPath, remoteUrl -func (_m *MockGit) AddRemoteLink(repoPath string, remoteUrl string) error { - ret := _m.Called(repoPath, remoteUrl) +// AddRemoteLink provides a mock function with given fields: ctx, directory, remoteURL +func (_m *MockGit) AddRemoteLink(ctx context.Context, directory string, remoteURL string) error { + ret := _m.Called(ctx, directory, remoteURL) if len(ret) == 0 { panic("no return value specified for AddRemoteLink") } var r0 error - if rf, ok := ret.Get(0).(func(string, string) error); ok { - r0 = rf(repoPath, remoteUrl) + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, directory, remoteURL) } else { r0 = ret.Error(0) } @@ -46,15 +46,16 @@ type MockGit_AddRemoteLink_Call struct { } // AddRemoteLink is a helper method to define mock.On call -// - repoPath string -// - remoteUrl string -func (_e *MockGit_Expecter) AddRemoteLink(repoPath interface{}, remoteUrl interface{}) *MockGit_AddRemoteLink_Call { - return &MockGit_AddRemoteLink_Call{Call: _e.mock.On("AddRemoteLink", repoPath, remoteUrl)} +// - ctx context.Context +// - directory string +// - remoteURL string +func (_e *MockGit_Expecter) AddRemoteLink(ctx interface{}, directory interface{}, remoteURL interface{}) *MockGit_AddRemoteLink_Call { + return &MockGit_AddRemoteLink_Call{Call: _e.mock.On("AddRemoteLink", ctx, directory, remoteURL)} } -func (_c *MockGit_AddRemoteLink_Call) Run(run func(repoPath string, remoteUrl string)) *MockGit_AddRemoteLink_Call { +func (_c *MockGit_AddRemoteLink_Call) Run(run func(ctx context.Context, directory string, remoteURL string)) *MockGit_AddRemoteLink_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string)) + run(args[0].(context.Context), args[1].(string), args[2].(string)) }) return _c } @@ -64,24 +65,24 @@ func (_c *MockGit_AddRemoteLink_Call) Return(_a0 error) *MockGit_AddRemoteLink_C return _c } -func (_c *MockGit_AddRemoteLink_Call) RunAndReturn(run func(string, string) error) *MockGit_AddRemoteLink_Call { +func (_c *MockGit_AddRemoteLink_Call) RunAndReturn(run func(context.Context, string, string) error) *MockGit_AddRemoteLink_Call { _c.Call.Return(run) return _c } -// CheckPermissions provides a mock function with given fields: ctx, repo, user, pass -func (_m *MockGit) CheckPermissions(ctx context.Context, repo string, user *string, pass *string) bool { - ret := _m.Called(ctx, repo, user, pass) +// CheckPermissions provides a mock function with given fields: ctx, repoURL +func (_m *MockGit) CheckPermissions(ctx context.Context, repoURL string) error { + ret := _m.Called(ctx, repoURL) if len(ret) == 0 { panic("no return value specified for CheckPermissions") } - var r0 bool - if rf, ok := ret.Get(0).(func(context.Context, string, *string, *string) bool); ok { - r0 = rf(ctx, repo, user, pass) + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, repoURL) } else { - r0 = ret.Get(0).(bool) + r0 = ret.Error(0) } return r0 @@ -94,41 +95,39 @@ type MockGit_CheckPermissions_Call struct { // CheckPermissions is a helper method to define mock.On call // - ctx context.Context -// - repo string -// - user *string -// - pass *string -func (_e *MockGit_Expecter) CheckPermissions(ctx interface{}, repo interface{}, user interface{}, pass interface{}) *MockGit_CheckPermissions_Call { - return &MockGit_CheckPermissions_Call{Call: _e.mock.On("CheckPermissions", ctx, repo, user, pass)} +// - repoURL string +func (_e *MockGit_Expecter) CheckPermissions(ctx interface{}, repoURL interface{}) *MockGit_CheckPermissions_Call { + return &MockGit_CheckPermissions_Call{Call: _e.mock.On("CheckPermissions", ctx, repoURL)} } -func (_c *MockGit_CheckPermissions_Call) Run(run func(ctx context.Context, repo string, user *string, pass *string)) *MockGit_CheckPermissions_Call { +func (_c *MockGit_CheckPermissions_Call) Run(run func(ctx context.Context, repoURL string)) *MockGit_CheckPermissions_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(*string), args[3].(*string)) + run(args[0].(context.Context), args[1].(string)) }) return _c } -func (_c *MockGit_CheckPermissions_Call) Return(accessible bool) *MockGit_CheckPermissions_Call { - _c.Call.Return(accessible) +func (_c *MockGit_CheckPermissions_Call) Return(_a0 error) *MockGit_CheckPermissions_Call { + _c.Call.Return(_a0) return _c } -func (_c *MockGit_CheckPermissions_Call) RunAndReturn(run func(context.Context, string, *string, *string) bool) *MockGit_CheckPermissions_Call { +func (_c *MockGit_CheckPermissions_Call) RunAndReturn(run func(context.Context, string) error) *MockGit_CheckPermissions_Call { _c.Call.Return(run) return _c } -// CheckReference provides a mock function with given fields: workDir, from -func (_m *MockGit) CheckReference(workDir string, from string) error { - ret := _m.Called(workDir, from) +// CheckReference provides a mock function with given fields: ctx, directory, refName +func (_m *MockGit) CheckReference(ctx context.Context, directory string, refName string) error { + ret := _m.Called(ctx, directory, refName) if len(ret) == 0 { panic("no return value specified for CheckReference") } var r0 error - if rf, ok := ret.Get(0).(func(string, string) error); ok { - r0 = rf(workDir, from) + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, directory, refName) } else { r0 = ret.Error(0) } @@ -142,15 +141,16 @@ type MockGit_CheckReference_Call struct { } // CheckReference is a helper method to define mock.On call -// - workDir string -// - from string -func (_e *MockGit_Expecter) CheckReference(workDir interface{}, from interface{}) *MockGit_CheckReference_Call { - return &MockGit_CheckReference_Call{Call: _e.mock.On("CheckReference", workDir, from)} +// - ctx context.Context +// - directory string +// - refName string +func (_e *MockGit_Expecter) CheckReference(ctx interface{}, directory interface{}, refName interface{}) *MockGit_CheckReference_Call { + return &MockGit_CheckReference_Call{Call: _e.mock.On("CheckReference", ctx, directory, refName)} } -func (_c *MockGit_CheckReference_Call) Run(run func(workDir string, from string)) *MockGit_CheckReference_Call { +func (_c *MockGit_CheckReference_Call) Run(run func(ctx context.Context, directory string, refName string)) *MockGit_CheckReference_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string)) + run(args[0].(context.Context), args[1].(string), args[2].(string)) }) return _c } @@ -160,22 +160,22 @@ func (_c *MockGit_CheckReference_Call) Return(_a0 error) *MockGit_CheckReference return _c } -func (_c *MockGit_CheckReference_Call) RunAndReturn(run func(string, string) error) *MockGit_CheckReference_Call { +func (_c *MockGit_CheckReference_Call) RunAndReturn(run func(context.Context, string, string) error) *MockGit_CheckReference_Call { _c.Call.Return(run) return _c } -// Checkout provides a mock function with given fields: user, pass, directory, branchName, remote -func (_m *MockGit) Checkout(user *string, pass *string, directory string, branchName string, remote bool) error { - ret := _m.Called(user, pass, directory, branchName, remote) +// Checkout provides a mock function with given fields: ctx, directory, branchName, remote +func (_m *MockGit) Checkout(ctx context.Context, directory string, branchName string, remote bool) error { + ret := _m.Called(ctx, directory, branchName, remote) if len(ret) == 0 { panic("no return value specified for Checkout") } var r0 error - if rf, ok := ret.Get(0).(func(*string, *string, string, string, bool) error); ok { - r0 = rf(user, pass, directory, branchName, remote) + if rf, ok := ret.Get(0).(func(context.Context, string, string, bool) error); ok { + r0 = rf(ctx, directory, branchName, remote) } else { r0 = ret.Error(0) } @@ -189,18 +189,17 @@ type MockGit_Checkout_Call struct { } // Checkout is a helper method to define mock.On call -// - user *string -// - pass *string +// - ctx context.Context // - directory string // - branchName string // - remote bool -func (_e *MockGit_Expecter) Checkout(user interface{}, pass interface{}, directory interface{}, branchName interface{}, remote interface{}) *MockGit_Checkout_Call { - return &MockGit_Checkout_Call{Call: _e.mock.On("Checkout", user, pass, directory, branchName, remote)} +func (_e *MockGit_Expecter) Checkout(ctx interface{}, directory interface{}, branchName interface{}, remote interface{}) *MockGit_Checkout_Call { + return &MockGit_Checkout_Call{Call: _e.mock.On("Checkout", ctx, directory, branchName, remote)} } -func (_c *MockGit_Checkout_Call) Run(run func(user *string, pass *string, directory string, branchName string, remote bool)) *MockGit_Checkout_Call { +func (_c *MockGit_Checkout_Call) Run(run func(ctx context.Context, directory string, branchName string, remote bool)) *MockGit_Checkout_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*string), args[1].(*string), args[2].(string), args[3].(string), args[4].(bool)) + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(bool)) }) return _c } @@ -210,71 +209,22 @@ func (_c *MockGit_Checkout_Call) Return(_a0 error) *MockGit_Checkout_Call { return _c } -func (_c *MockGit_Checkout_Call) RunAndReturn(run func(*string, *string, string, string, bool) error) *MockGit_Checkout_Call { - _c.Call.Return(run) - return _c -} - -// CheckoutRemoteBranchBySSH provides a mock function with given fields: key, user, gitPath, remoteBranchName -func (_m *MockGit) CheckoutRemoteBranchBySSH(key string, user string, gitPath string, remoteBranchName string) error { - ret := _m.Called(key, user, gitPath, remoteBranchName) - - if len(ret) == 0 { - panic("no return value specified for CheckoutRemoteBranchBySSH") - } - - var r0 error - if rf, ok := ret.Get(0).(func(string, string, string, string) error); ok { - r0 = rf(key, user, gitPath, remoteBranchName) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_CheckoutRemoteBranchBySSH_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckoutRemoteBranchBySSH' -type MockGit_CheckoutRemoteBranchBySSH_Call struct { - *mock.Call -} - -// CheckoutRemoteBranchBySSH is a helper method to define mock.On call -// - key string -// - user string -// - gitPath string -// - remoteBranchName string -func (_e *MockGit_Expecter) CheckoutRemoteBranchBySSH(key interface{}, user interface{}, gitPath interface{}, remoteBranchName interface{}) *MockGit_CheckoutRemoteBranchBySSH_Call { - return &MockGit_CheckoutRemoteBranchBySSH_Call{Call: _e.mock.On("CheckoutRemoteBranchBySSH", key, user, gitPath, remoteBranchName)} -} - -func (_c *MockGit_CheckoutRemoteBranchBySSH_Call) Run(run func(key string, user string, gitPath string, remoteBranchName string)) *MockGit_CheckoutRemoteBranchBySSH_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(string), args[3].(string)) - }) - return _c -} - -func (_c *MockGit_CheckoutRemoteBranchBySSH_Call) Return(_a0 error) *MockGit_CheckoutRemoteBranchBySSH_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_CheckoutRemoteBranchBySSH_Call) RunAndReturn(run func(string, string, string, string) error) *MockGit_CheckoutRemoteBranchBySSH_Call { +func (_c *MockGit_Checkout_Call) RunAndReturn(run func(context.Context, string, string, bool) error) *MockGit_Checkout_Call { _c.Call.Return(run) return _c } -// CloneRepository provides a mock function with given fields: repo, user, pass, destination -func (_m *MockGit) CloneRepository(repo string, user *string, pass *string, destination string) error { - ret := _m.Called(repo, user, pass, destination) +// CheckoutRemoteBranch provides a mock function with given fields: ctx, directory, branchName +func (_m *MockGit) CheckoutRemoteBranch(ctx context.Context, directory string, branchName string) error { + ret := _m.Called(ctx, directory, branchName) if len(ret) == 0 { - panic("no return value specified for CloneRepository") + panic("no return value specified for CheckoutRemoteBranch") } var r0 error - if rf, ok := ret.Get(0).(func(string, *string, *string, string) error); ok { - r0 = rf(repo, user, pass, destination) + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, directory, branchName) } else { r0 = ret.Error(0) } @@ -282,48 +232,47 @@ func (_m *MockGit) CloneRepository(repo string, user *string, pass *string, dest return r0 } -// MockGit_CloneRepository_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloneRepository' -type MockGit_CloneRepository_Call struct { +// MockGit_CheckoutRemoteBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckoutRemoteBranch' +type MockGit_CheckoutRemoteBranch_Call struct { *mock.Call } -// CloneRepository is a helper method to define mock.On call -// - repo string -// - user *string -// - pass *string -// - destination string -func (_e *MockGit_Expecter) CloneRepository(repo interface{}, user interface{}, pass interface{}, destination interface{}) *MockGit_CloneRepository_Call { - return &MockGit_CloneRepository_Call{Call: _e.mock.On("CloneRepository", repo, user, pass, destination)} +// CheckoutRemoteBranch is a helper method to define mock.On call +// - ctx context.Context +// - directory string +// - branchName string +func (_e *MockGit_Expecter) CheckoutRemoteBranch(ctx interface{}, directory interface{}, branchName interface{}) *MockGit_CheckoutRemoteBranch_Call { + return &MockGit_CheckoutRemoteBranch_Call{Call: _e.mock.On("CheckoutRemoteBranch", ctx, directory, branchName)} } -func (_c *MockGit_CloneRepository_Call) Run(run func(repo string, user *string, pass *string, destination string)) *MockGit_CloneRepository_Call { +func (_c *MockGit_CheckoutRemoteBranch_Call) Run(run func(ctx context.Context, directory string, branchName string)) *MockGit_CheckoutRemoteBranch_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(*string), args[2].(*string), args[3].(string)) + run(args[0].(context.Context), args[1].(string), args[2].(string)) }) return _c } -func (_c *MockGit_CloneRepository_Call) Return(_a0 error) *MockGit_CloneRepository_Call { +func (_c *MockGit_CheckoutRemoteBranch_Call) Return(_a0 error) *MockGit_CheckoutRemoteBranch_Call { _c.Call.Return(_a0) return _c } -func (_c *MockGit_CloneRepository_Call) RunAndReturn(run func(string, *string, *string, string) error) *MockGit_CloneRepository_Call { +func (_c *MockGit_CheckoutRemoteBranch_Call) RunAndReturn(run func(context.Context, string, string) error) *MockGit_CheckoutRemoteBranch_Call { _c.Call.Return(run) return _c } -// CloneRepositoryBySsh provides a mock function with given fields: ctx, key, user, repoUrl, destination, port -func (_m *MockGit) CloneRepositoryBySsh(ctx context.Context, key string, user string, repoUrl string, destination string, port int32) error { - ret := _m.Called(ctx, key, user, repoUrl, destination, port) +// Clone provides a mock function with given fields: ctx, repoURL, destination +func (_m *MockGit) Clone(ctx context.Context, repoURL string, destination string) error { + ret := _m.Called(ctx, repoURL, destination) if len(ret) == 0 { - panic("no return value specified for CloneRepositoryBySsh") + panic("no return value specified for Clone") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, int32) error); ok { - r0 = rf(ctx, key, user, repoUrl, destination, port) + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, repoURL, destination) } else { r0 = ret.Error(0) } @@ -331,57 +280,54 @@ func (_m *MockGit) CloneRepositoryBySsh(ctx context.Context, key string, user st return r0 } -// MockGit_CloneRepositoryBySsh_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloneRepositoryBySsh' -type MockGit_CloneRepositoryBySsh_Call struct { +// MockGit_Clone_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Clone' +type MockGit_Clone_Call struct { *mock.Call } -// CloneRepositoryBySsh is a helper method to define mock.On call +// Clone is a helper method to define mock.On call // - ctx context.Context -// - key string -// - user string -// - repoUrl string +// - repoURL string // - destination string -// - port int32 -func (_e *MockGit_Expecter) CloneRepositoryBySsh(ctx interface{}, key interface{}, user interface{}, repoUrl interface{}, destination interface{}, port interface{}) *MockGit_CloneRepositoryBySsh_Call { - return &MockGit_CloneRepositoryBySsh_Call{Call: _e.mock.On("CloneRepositoryBySsh", ctx, key, user, repoUrl, destination, port)} +func (_e *MockGit_Expecter) Clone(ctx interface{}, repoURL interface{}, destination interface{}) *MockGit_Clone_Call { + return &MockGit_Clone_Call{Call: _e.mock.On("Clone", ctx, repoURL, destination)} } -func (_c *MockGit_CloneRepositoryBySsh_Call) Run(run func(ctx context.Context, key string, user string, repoUrl string, destination string, port int32)) *MockGit_CloneRepositoryBySsh_Call { +func (_c *MockGit_Clone_Call) Run(run func(ctx context.Context, repoURL string, destination string)) *MockGit_Clone_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string), args[5].(int32)) + run(args[0].(context.Context), args[1].(string), args[2].(string)) }) return _c } -func (_c *MockGit_CloneRepositoryBySsh_Call) Return(_a0 error) *MockGit_CloneRepositoryBySsh_Call { +func (_c *MockGit_Clone_Call) Return(_a0 error) *MockGit_Clone_Call { _c.Call.Return(_a0) return _c } -func (_c *MockGit_CloneRepositoryBySsh_Call) RunAndReturn(run func(context.Context, string, string, string, string, int32) error) *MockGit_CloneRepositoryBySsh_Call { +func (_c *MockGit_Clone_Call) RunAndReturn(run func(context.Context, string, string) error) *MockGit_Clone_Call { _c.Call.Return(run) return _c } -// CommitChanges provides a mock function with given fields: directory, commitMsg, opts -func (_m *MockGit) CommitChanges(directory string, commitMsg string, opts ...git.CommitOps) error { - _va := make([]interface{}, len(opts)) - for _i := range opts { - _va[_i] = opts[_i] +// Commit provides a mock function with given fields: ctx, directory, message, ops +func (_m *MockGit) Commit(ctx context.Context, directory string, message string, ops ...v2.CommitOps) error { + _va := make([]interface{}, len(ops)) + for _i := range ops { + _va[_i] = ops[_i] } var _ca []interface{} - _ca = append(_ca, directory, commitMsg) + _ca = append(_ca, ctx, directory, message) _ca = append(_ca, _va...) ret := _m.Called(_ca...) if len(ret) == 0 { - panic("no return value specified for CommitChanges") + panic("no return value specified for Commit") } var r0 error - if rf, ok := ret.Get(0).(func(string, string, ...git.CommitOps) error); ok { - r0 = rf(directory, commitMsg, opts...) + if rf, ok := ret.Get(0).(func(context.Context, string, string, ...v2.CommitOps) error); ok { + r0 = rf(ctx, directory, message, ops...) } else { r0 = ret.Error(0) } @@ -389,46 +335,47 @@ func (_m *MockGit) CommitChanges(directory string, commitMsg string, opts ...git return r0 } -// MockGit_CommitChanges_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CommitChanges' -type MockGit_CommitChanges_Call struct { +// MockGit_Commit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Commit' +type MockGit_Commit_Call struct { *mock.Call } -// CommitChanges is a helper method to define mock.On call +// Commit is a helper method to define mock.On call +// - ctx context.Context // - directory string -// - commitMsg string -// - opts ...git.CommitOps -func (_e *MockGit_Expecter) CommitChanges(directory interface{}, commitMsg interface{}, opts ...interface{}) *MockGit_CommitChanges_Call { - return &MockGit_CommitChanges_Call{Call: _e.mock.On("CommitChanges", - append([]interface{}{directory, commitMsg}, opts...)...)} +// - message string +// - ops ...v2.CommitOps +func (_e *MockGit_Expecter) Commit(ctx interface{}, directory interface{}, message interface{}, ops ...interface{}) *MockGit_Commit_Call { + return &MockGit_Commit_Call{Call: _e.mock.On("Commit", + append([]interface{}{ctx, directory, message}, ops...)...)} } -func (_c *MockGit_CommitChanges_Call) Run(run func(directory string, commitMsg string, opts ...git.CommitOps)) *MockGit_CommitChanges_Call { +func (_c *MockGit_Commit_Call) Run(run func(ctx context.Context, directory string, message string, ops ...v2.CommitOps)) *MockGit_Commit_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]git.CommitOps, len(args)-2) - for i, a := range args[2:] { + variadicArgs := make([]v2.CommitOps, len(args)-3) + for i, a := range args[3:] { if a != nil { - variadicArgs[i] = a.(git.CommitOps) + variadicArgs[i] = a.(v2.CommitOps) } } - run(args[0].(string), args[1].(string), variadicArgs...) + run(args[0].(context.Context), args[1].(string), args[2].(string), variadicArgs...) }) return _c } -func (_c *MockGit_CommitChanges_Call) Return(_a0 error) *MockGit_CommitChanges_Call { +func (_c *MockGit_Commit_Call) Return(_a0 error) *MockGit_Commit_Call { _c.Call.Return(_a0) return _c } -func (_c *MockGit_CommitChanges_Call) RunAndReturn(run func(string, string, ...git.CommitOps) error) *MockGit_CommitChanges_Call { +func (_c *MockGit_Commit_Call) RunAndReturn(run func(context.Context, string, string, ...v2.CommitOps) error) *MockGit_Commit_Call { _c.Call.Return(run) return _c } -// CommitExists provides a mock function with given fields: directory, hash -func (_m *MockGit) CommitExists(directory string, hash string) (bool, error) { - ret := _m.Called(directory, hash) +// CommitExists provides a mock function with given fields: ctx, directory, hash +func (_m *MockGit) CommitExists(ctx context.Context, directory string, hash string) (bool, error) { + ret := _m.Called(ctx, directory, hash) if len(ret) == 0 { panic("no return value specified for CommitExists") @@ -436,17 +383,17 @@ func (_m *MockGit) CommitExists(directory string, hash string) (bool, error) { var r0 bool var r1 error - if rf, ok := ret.Get(0).(func(string, string) (bool, error)); ok { - return rf(directory, hash) + if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok { + return rf(ctx, directory, hash) } - if rf, ok := ret.Get(0).(func(string, string) bool); ok { - r0 = rf(directory, hash) + if rf, ok := ret.Get(0).(func(context.Context, string, string) bool); ok { + r0 = rf(ctx, directory, hash) } else { r0 = ret.Get(0).(bool) } - if rf, ok := ret.Get(1).(func(string, string) error); ok { - r1 = rf(directory, hash) + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, directory, hash) } else { r1 = ret.Error(1) } @@ -460,15 +407,16 @@ type MockGit_CommitExists_Call struct { } // CommitExists is a helper method to define mock.On call +// - ctx context.Context // - directory string // - hash string -func (_e *MockGit_Expecter) CommitExists(directory interface{}, hash interface{}) *MockGit_CommitExists_Call { - return &MockGit_CommitExists_Call{Call: _e.mock.On("CommitExists", directory, hash)} +func (_e *MockGit_Expecter) CommitExists(ctx interface{}, directory interface{}, hash interface{}) *MockGit_CommitExists_Call { + return &MockGit_CommitExists_Call{Call: _e.mock.On("CommitExists", ctx, directory, hash)} } -func (_c *MockGit_CommitExists_Call) Run(run func(directory string, hash string)) *MockGit_CommitExists_Call { +func (_c *MockGit_CommitExists_Call) Run(run func(ctx context.Context, directory string, hash string)) *MockGit_CommitExists_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string)) + run(args[0].(context.Context), args[1].(string), args[2].(string)) }) return _c } @@ -478,22 +426,22 @@ func (_c *MockGit_CommitExists_Call) Return(_a0 bool, _a1 error) *MockGit_Commit return _c } -func (_c *MockGit_CommitExists_Call) RunAndReturn(run func(string, string) (bool, error)) *MockGit_CommitExists_Call { +func (_c *MockGit_CommitExists_Call) RunAndReturn(run func(context.Context, string, string) (bool, error)) *MockGit_CommitExists_Call { _c.Call.Return(run) return _c } -// CreateChildBranch provides a mock function with given fields: directory, currentBranch, newBranch -func (_m *MockGit) CreateChildBranch(directory string, currentBranch string, newBranch string) error { - ret := _m.Called(directory, currentBranch, newBranch) +// CreateChildBranch provides a mock function with given fields: ctx, directory, parentBranch, newBranch +func (_m *MockGit) CreateChildBranch(ctx context.Context, directory string, parentBranch string, newBranch string) error { + ret := _m.Called(ctx, directory, parentBranch, newBranch) if len(ret) == 0 { panic("no return value specified for CreateChildBranch") } var r0 error - if rf, ok := ret.Get(0).(func(string, string, string) error); ok { - r0 = rf(directory, currentBranch, newBranch) + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { + r0 = rf(ctx, directory, parentBranch, newBranch) } else { r0 = ret.Error(0) } @@ -507,16 +455,17 @@ type MockGit_CreateChildBranch_Call struct { } // CreateChildBranch is a helper method to define mock.On call +// - ctx context.Context // - directory string -// - currentBranch string +// - parentBranch string // - newBranch string -func (_e *MockGit_Expecter) CreateChildBranch(directory interface{}, currentBranch interface{}, newBranch interface{}) *MockGit_CreateChildBranch_Call { - return &MockGit_CreateChildBranch_Call{Call: _e.mock.On("CreateChildBranch", directory, currentBranch, newBranch)} +func (_e *MockGit_Expecter) CreateChildBranch(ctx interface{}, directory interface{}, parentBranch interface{}, newBranch interface{}) *MockGit_CreateChildBranch_Call { + return &MockGit_CreateChildBranch_Call{Call: _e.mock.On("CreateChildBranch", ctx, directory, parentBranch, newBranch)} } -func (_c *MockGit_CreateChildBranch_Call) Run(run func(directory string, currentBranch string, newBranch string)) *MockGit_CreateChildBranch_Call { +func (_c *MockGit_CreateChildBranch_Call) Run(run func(ctx context.Context, directory string, parentBranch string, newBranch string)) *MockGit_CreateChildBranch_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(string)) + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string)) }) return _c } @@ -526,22 +475,22 @@ func (_c *MockGit_CreateChildBranch_Call) Return(_a0 error) *MockGit_CreateChild return _c } -func (_c *MockGit_CreateChildBranch_Call) RunAndReturn(run func(string, string, string) error) *MockGit_CreateChildBranch_Call { +func (_c *MockGit_CreateChildBranch_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockGit_CreateChildBranch_Call { _c.Call.Return(run) return _c } -// CreateRemoteBranch provides a mock function with given fields: key, user, path, name, fromcommit, port -func (_m *MockGit) CreateRemoteBranch(key string, user string, path string, name string, fromcommit string, port int32) error { - ret := _m.Called(key, user, path, name, fromcommit, port) +// CreateRemoteBranch provides a mock function with given fields: ctx, directory, branchName, fromRef +func (_m *MockGit) CreateRemoteBranch(ctx context.Context, directory string, branchName string, fromRef string) error { + ret := _m.Called(ctx, directory, branchName, fromRef) if len(ret) == 0 { panic("no return value specified for CreateRemoteBranch") } var r0 error - if rf, ok := ret.Get(0).(func(string, string, string, string, string, int32) error); ok { - r0 = rf(key, user, path, name, fromcommit, port) + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { + r0 = rf(ctx, directory, branchName, fromRef) } else { r0 = ret.Error(0) } @@ -555,19 +504,17 @@ type MockGit_CreateRemoteBranch_Call struct { } // CreateRemoteBranch is a helper method to define mock.On call -// - key string -// - user string -// - path string -// - name string -// - fromcommit string -// - port int32 -func (_e *MockGit_Expecter) CreateRemoteBranch(key interface{}, user interface{}, path interface{}, name interface{}, fromcommit interface{}, port interface{}) *MockGit_CreateRemoteBranch_Call { - return &MockGit_CreateRemoteBranch_Call{Call: _e.mock.On("CreateRemoteBranch", key, user, path, name, fromcommit, port)} +// - ctx context.Context +// - directory string +// - branchName string +// - fromRef string +func (_e *MockGit_Expecter) CreateRemoteBranch(ctx interface{}, directory interface{}, branchName interface{}, fromRef interface{}) *MockGit_CreateRemoteBranch_Call { + return &MockGit_CreateRemoteBranch_Call{Call: _e.mock.On("CreateRemoteBranch", ctx, directory, branchName, fromRef)} } -func (_c *MockGit_CreateRemoteBranch_Call) Run(run func(key string, user string, path string, name string, fromcommit string, port int32)) *MockGit_CreateRemoteBranch_Call { +func (_c *MockGit_CreateRemoteBranch_Call) Run(run func(ctx context.Context, directory string, branchName string, fromRef string)) *MockGit_CreateRemoteBranch_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(string), args[3].(string), args[4].(string), args[5].(int32)) + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string)) }) return _c } @@ -577,22 +524,22 @@ func (_c *MockGit_CreateRemoteBranch_Call) Return(_a0 error) *MockGit_CreateRemo return _c } -func (_c *MockGit_CreateRemoteBranch_Call) RunAndReturn(run func(string, string, string, string, string, int32) error) *MockGit_CreateRemoteBranch_Call { +func (_c *MockGit_CreateRemoteBranch_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockGit_CreateRemoteBranch_Call { _c.Call.Return(run) return _c } -// CreateRemoteTag provides a mock function with given fields: key, user, path, branchName, name -func (_m *MockGit) CreateRemoteTag(key string, user string, path string, branchName string, name string) error { - ret := _m.Called(key, user, path, branchName, name) +// CreateRemoteTag provides a mock function with given fields: ctx, directory, branchName, tagName +func (_m *MockGit) CreateRemoteTag(ctx context.Context, directory string, branchName string, tagName string) error { + ret := _m.Called(ctx, directory, branchName, tagName) if len(ret) == 0 { panic("no return value specified for CreateRemoteTag") } var r0 error - if rf, ok := ret.Get(0).(func(string, string, string, string, string) error); ok { - r0 = rf(key, user, path, branchName, name) + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { + r0 = rf(ctx, directory, branchName, tagName) } else { r0 = ret.Error(0) } @@ -606,18 +553,17 @@ type MockGit_CreateRemoteTag_Call struct { } // CreateRemoteTag is a helper method to define mock.On call -// - key string -// - user string -// - path string +// - ctx context.Context +// - directory string // - branchName string -// - name string -func (_e *MockGit_Expecter) CreateRemoteTag(key interface{}, user interface{}, path interface{}, branchName interface{}, name interface{}) *MockGit_CreateRemoteTag_Call { - return &MockGit_CreateRemoteTag_Call{Call: _e.mock.On("CreateRemoteTag", key, user, path, branchName, name)} +// - tagName string +func (_e *MockGit_Expecter) CreateRemoteTag(ctx interface{}, directory interface{}, branchName interface{}, tagName interface{}) *MockGit_CreateRemoteTag_Call { + return &MockGit_CreateRemoteTag_Call{Call: _e.mock.On("CreateRemoteTag", ctx, directory, branchName, tagName)} } -func (_c *MockGit_CreateRemoteTag_Call) Run(run func(key string, user string, path string, branchName string, name string)) *MockGit_CreateRemoteTag_Call { +func (_c *MockGit_CreateRemoteTag_Call) Run(run func(ctx context.Context, directory string, branchName string, tagName string)) *MockGit_CreateRemoteTag_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(string), args[3].(string), args[4].(string)) + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string)) }) return _c } @@ -627,22 +573,22 @@ func (_c *MockGit_CreateRemoteTag_Call) Return(_a0 error) *MockGit_CreateRemoteT return _c } -func (_c *MockGit_CreateRemoteTag_Call) RunAndReturn(run func(string, string, string, string, string) error) *MockGit_CreateRemoteTag_Call { +func (_c *MockGit_CreateRemoteTag_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockGit_CreateRemoteTag_Call { _c.Call.Return(run) return _c } -// Fetch provides a mock function with given fields: key, user, path, branchName -func (_m *MockGit) Fetch(key string, user string, path string, branchName string) error { - ret := _m.Called(key, user, path, branchName) +// Fetch provides a mock function with given fields: ctx, directory, branchName +func (_m *MockGit) Fetch(ctx context.Context, directory string, branchName string) error { + ret := _m.Called(ctx, directory, branchName) if len(ret) == 0 { panic("no return value specified for Fetch") } var r0 error - if rf, ok := ret.Get(0).(func(string, string, string, string) error); ok { - r0 = rf(key, user, path, branchName) + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, directory, branchName) } else { r0 = ret.Error(0) } @@ -656,17 +602,16 @@ type MockGit_Fetch_Call struct { } // Fetch is a helper method to define mock.On call -// - key string -// - user string -// - path string +// - ctx context.Context +// - directory string // - branchName string -func (_e *MockGit_Expecter) Fetch(key interface{}, user interface{}, path interface{}, branchName interface{}) *MockGit_Fetch_Call { - return &MockGit_Fetch_Call{Call: _e.mock.On("Fetch", key, user, path, branchName)} +func (_e *MockGit_Expecter) Fetch(ctx interface{}, directory interface{}, branchName interface{}) *MockGit_Fetch_Call { + return &MockGit_Fetch_Call{Call: _e.mock.On("Fetch", ctx, directory, branchName)} } -func (_c *MockGit_Fetch_Call) Run(run func(key string, user string, path string, branchName string)) *MockGit_Fetch_Call { +func (_c *MockGit_Fetch_Call) Run(run func(ctx context.Context, directory string, branchName string)) *MockGit_Fetch_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(string), args[3].(string)) + run(args[0].(context.Context), args[1].(string), args[2].(string)) }) return _c } @@ -676,14 +621,14 @@ func (_c *MockGit_Fetch_Call) Return(_a0 error) *MockGit_Fetch_Call { return _c } -func (_c *MockGit_Fetch_Call) RunAndReturn(run func(string, string, string, string) error) *MockGit_Fetch_Call { +func (_c *MockGit_Fetch_Call) RunAndReturn(run func(context.Context, string, string) error) *MockGit_Fetch_Call { _c.Call.Return(run) return _c } -// GetCurrentBranchName provides a mock function with given fields: directory -func (_m *MockGit) GetCurrentBranchName(directory string) (string, error) { - ret := _m.Called(directory) +// GetCurrentBranchName provides a mock function with given fields: ctx, directory +func (_m *MockGit) GetCurrentBranchName(ctx context.Context, directory string) (string, error) { + ret := _m.Called(ctx, directory) if len(ret) == 0 { panic("no return value specified for GetCurrentBranchName") @@ -691,17 +636,17 @@ func (_m *MockGit) GetCurrentBranchName(directory string) (string, error) { var r0 string var r1 error - if rf, ok := ret.Get(0).(func(string) (string, error)); ok { - return rf(directory) + if rf, ok := ret.Get(0).(func(context.Context, string) (string, error)); ok { + return rf(ctx, directory) } - if rf, ok := ret.Get(0).(func(string) string); ok { - r0 = rf(directory) + if rf, ok := ret.Get(0).(func(context.Context, string) string); ok { + r0 = rf(ctx, directory) } else { r0 = ret.Get(0).(string) } - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(directory) + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, directory) } else { r1 = ret.Error(1) } @@ -715,14 +660,15 @@ type MockGit_GetCurrentBranchName_Call struct { } // GetCurrentBranchName is a helper method to define mock.On call +// - ctx context.Context // - directory string -func (_e *MockGit_Expecter) GetCurrentBranchName(directory interface{}) *MockGit_GetCurrentBranchName_Call { - return &MockGit_GetCurrentBranchName_Call{Call: _e.mock.On("GetCurrentBranchName", directory)} +func (_e *MockGit_Expecter) GetCurrentBranchName(ctx interface{}, directory interface{}) *MockGit_GetCurrentBranchName_Call { + return &MockGit_GetCurrentBranchName_Call{Call: _e.mock.On("GetCurrentBranchName", ctx, directory)} } -func (_c *MockGit_GetCurrentBranchName_Call) Run(run func(directory string)) *MockGit_GetCurrentBranchName_Call { +func (_c *MockGit_GetCurrentBranchName_Call) Run(run func(ctx context.Context, directory string)) *MockGit_GetCurrentBranchName_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + run(args[0].(context.Context), args[1].(string)) }) return _c } @@ -732,22 +678,22 @@ func (_c *MockGit_GetCurrentBranchName_Call) Return(_a0 string, _a1 error) *Mock return _c } -func (_c *MockGit_GetCurrentBranchName_Call) RunAndReturn(run func(string) (string, error)) *MockGit_GetCurrentBranchName_Call { +func (_c *MockGit_GetCurrentBranchName_Call) RunAndReturn(run func(context.Context, string) (string, error)) *MockGit_GetCurrentBranchName_Call { _c.Call.Return(run) return _c } -// Init provides a mock function with given fields: directory -func (_m *MockGit) Init(directory string) error { - ret := _m.Called(directory) +// Init provides a mock function with given fields: ctx, directory +func (_m *MockGit) Init(ctx context.Context, directory string) error { + ret := _m.Called(ctx, directory) if len(ret) == 0 { panic("no return value specified for Init") } var r0 error - if rf, ok := ret.Get(0).(func(string) error); ok { - r0 = rf(directory) + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, directory) } else { r0 = ret.Error(0) } @@ -761,14 +707,15 @@ type MockGit_Init_Call struct { } // Init is a helper method to define mock.On call +// - ctx context.Context // - directory string -func (_e *MockGit_Expecter) Init(directory interface{}) *MockGit_Init_Call { - return &MockGit_Init_Call{Call: _e.mock.On("Init", directory)} +func (_e *MockGit_Expecter) Init(ctx interface{}, directory interface{}) *MockGit_Init_Call { + return &MockGit_Init_Call{Call: _e.mock.On("Init", ctx, directory)} } -func (_c *MockGit_Init_Call) Run(run func(directory string)) *MockGit_Init_Call { +func (_c *MockGit_Init_Call) Run(run func(ctx context.Context, directory string)) *MockGit_Init_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + run(args[0].(context.Context), args[1].(string)) }) return _c } @@ -778,29 +725,29 @@ func (_c *MockGit_Init_Call) Return(_a0 error) *MockGit_Init_Call { return _c } -func (_c *MockGit_Init_Call) RunAndReturn(run func(string) error) *MockGit_Init_Call { +func (_c *MockGit_Init_Call) RunAndReturn(run func(context.Context, string) error) *MockGit_Init_Call { _c.Call.Return(run) return _c } -// PushChanges provides a mock function with given fields: key, user, directory, port, pushParams -func (_m *MockGit) PushChanges(key string, user string, directory string, port int32, pushParams ...string) error { - _va := make([]interface{}, len(pushParams)) - for _i := range pushParams { - _va[_i] = pushParams[_i] +// Push provides a mock function with given fields: ctx, directory, refspecs +func (_m *MockGit) Push(ctx context.Context, directory string, refspecs ...string) error { + _va := make([]interface{}, len(refspecs)) + for _i := range refspecs { + _va[_i] = refspecs[_i] } var _ca []interface{} - _ca = append(_ca, key, user, directory, port) + _ca = append(_ca, ctx, directory) _ca = append(_ca, _va...) ret := _m.Called(_ca...) if len(ret) == 0 { - panic("no return value specified for PushChanges") + panic("no return value specified for Push") } var r0 error - if rf, ok := ret.Get(0).(func(string, string, string, int32, ...string) error); ok { - r0 = rf(key, user, directory, port, pushParams...) + if rf, ok := ret.Get(0).(func(context.Context, string, ...string) error); ok { + r0 = rf(ctx, directory, refspecs...) } else { r0 = ret.Error(0) } @@ -808,56 +755,54 @@ func (_m *MockGit) PushChanges(key string, user string, directory string, port i return r0 } -// MockGit_PushChanges_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PushChanges' -type MockGit_PushChanges_Call struct { +// MockGit_Push_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Push' +type MockGit_Push_Call struct { *mock.Call } -// PushChanges is a helper method to define mock.On call -// - key string -// - user string +// Push is a helper method to define mock.On call +// - ctx context.Context // - directory string -// - port int32 -// - pushParams ...string -func (_e *MockGit_Expecter) PushChanges(key interface{}, user interface{}, directory interface{}, port interface{}, pushParams ...interface{}) *MockGit_PushChanges_Call { - return &MockGit_PushChanges_Call{Call: _e.mock.On("PushChanges", - append([]interface{}{key, user, directory, port}, pushParams...)...)} +// - refspecs ...string +func (_e *MockGit_Expecter) Push(ctx interface{}, directory interface{}, refspecs ...interface{}) *MockGit_Push_Call { + return &MockGit_Push_Call{Call: _e.mock.On("Push", + append([]interface{}{ctx, directory}, refspecs...)...)} } -func (_c *MockGit_PushChanges_Call) Run(run func(key string, user string, directory string, port int32, pushParams ...string)) *MockGit_PushChanges_Call { +func (_c *MockGit_Push_Call) Run(run func(ctx context.Context, directory string, refspecs ...string)) *MockGit_Push_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]string, len(args)-4) - for i, a := range args[4:] { + variadicArgs := make([]string, len(args)-2) + for i, a := range args[2:] { if a != nil { variadicArgs[i] = a.(string) } } - run(args[0].(string), args[1].(string), args[2].(string), args[3].(int32), variadicArgs...) + run(args[0].(context.Context), args[1].(string), variadicArgs...) }) return _c } -func (_c *MockGit_PushChanges_Call) Return(_a0 error) *MockGit_PushChanges_Call { +func (_c *MockGit_Push_Call) Return(_a0 error) *MockGit_Push_Call { _c.Call.Return(_a0) return _c } -func (_c *MockGit_PushChanges_Call) RunAndReturn(run func(string, string, string, int32, ...string) error) *MockGit_PushChanges_Call { +func (_c *MockGit_Push_Call) RunAndReturn(run func(context.Context, string, ...string) error) *MockGit_Push_Call { _c.Call.Return(run) return _c } -// RemoveBranch provides a mock function with given fields: directory, branchName -func (_m *MockGit) RemoveBranch(directory string, branchName string) error { - ret := _m.Called(directory, branchName) +// RemoveBranch provides a mock function with given fields: ctx, directory, branchName +func (_m *MockGit) RemoveBranch(ctx context.Context, directory string, branchName string) error { + ret := _m.Called(ctx, directory, branchName) if len(ret) == 0 { panic("no return value specified for RemoveBranch") } var r0 error - if rf, ok := ret.Get(0).(func(string, string) error); ok { - r0 = rf(directory, branchName) + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, directory, branchName) } else { r0 = ret.Error(0) } @@ -871,15 +816,16 @@ type MockGit_RemoveBranch_Call struct { } // RemoveBranch is a helper method to define mock.On call +// - ctx context.Context // - directory string // - branchName string -func (_e *MockGit_Expecter) RemoveBranch(directory interface{}, branchName interface{}) *MockGit_RemoveBranch_Call { - return &MockGit_RemoveBranch_Call{Call: _e.mock.On("RemoveBranch", directory, branchName)} +func (_e *MockGit_Expecter) RemoveBranch(ctx interface{}, directory interface{}, branchName interface{}) *MockGit_RemoveBranch_Call { + return &MockGit_RemoveBranch_Call{Call: _e.mock.On("RemoveBranch", ctx, directory, branchName)} } -func (_c *MockGit_RemoveBranch_Call) Run(run func(directory string, branchName string)) *MockGit_RemoveBranch_Call { +func (_c *MockGit_RemoveBranch_Call) Run(run func(ctx context.Context, directory string, branchName string)) *MockGit_RemoveBranch_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string)) + run(args[0].(context.Context), args[1].(string), args[2].(string)) }) return _c } @@ -889,55 +835,7 @@ func (_c *MockGit_RemoveBranch_Call) Return(_a0 error) *MockGit_RemoveBranch_Cal return _c } -func (_c *MockGit_RemoveBranch_Call) RunAndReturn(run func(string, string) error) *MockGit_RemoveBranch_Call { - _c.Call.Return(run) - return _c -} - -// RenameBranch provides a mock function with given fields: directory, currentName, newName -func (_m *MockGit) RenameBranch(directory string, currentName string, newName string) error { - ret := _m.Called(directory, currentName, newName) - - if len(ret) == 0 { - panic("no return value specified for RenameBranch") - } - - var r0 error - if rf, ok := ret.Get(0).(func(string, string, string) error); ok { - r0 = rf(directory, currentName, newName) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_RenameBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RenameBranch' -type MockGit_RenameBranch_Call struct { - *mock.Call -} - -// RenameBranch is a helper method to define mock.On call -// - directory string -// - currentName string -// - newName string -func (_e *MockGit_Expecter) RenameBranch(directory interface{}, currentName interface{}, newName interface{}) *MockGit_RenameBranch_Call { - return &MockGit_RenameBranch_Call{Call: _e.mock.On("RenameBranch", directory, currentName, newName)} -} - -func (_c *MockGit_RenameBranch_Call) Run(run func(directory string, currentName string, newName string)) *MockGit_RenameBranch_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(string)) - }) - return _c -} - -func (_c *MockGit_RenameBranch_Call) Return(_a0 error) *MockGit_RenameBranch_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_RenameBranch_Call) RunAndReturn(run func(string, string, string) error) *MockGit_RenameBranch_Call { +func (_c *MockGit_RemoveBranch_Call) RunAndReturn(run func(context.Context, string, string) error) *MockGit_RemoveBranch_Call { _c.Call.Return(run) return _c } diff --git a/pkg/git/v2/provider.go b/pkg/git/provider.go similarity index 100% rename from pkg/git/v2/provider.go rename to pkg/git/provider.go diff --git a/pkg/git/provider_test.go b/pkg/git/provider_test.go new file mode 100644 index 00000000..2d4e36fb --- /dev/null +++ b/pkg/git/provider_test.go @@ -0,0 +1,1985 @@ +package v2 + +import ( + "context" + "encoding/base64" + "net/http" + "net/http/httptest" + "os" + "path" + "testing" + "time" + + gogit "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + githttp "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/epam/edp-codebase-operator/v2/pkg/platform" +) + +func TestGitProvider_CheckPermissions(t *testing.T) { + user := "user" + pass := "pass" + + config := Config{ + Username: user, + Token: pass, + } + gp := NewGitProvider(config) + + bts, err := base64.StdEncoding.DecodeString(`MDAxZSMgc2VydmljZT1naXQtdXBsb2FkLXBhY2sKMDAwMDAxNTY2ZWNmMGVmMmMyZGZmYjc5NjAzM2U1YTAyMjE5YWY4NmVjNjU4NGU1IEhFQUQAbXVsdGlfYWNrIHRoaW4tcGFjayBzaWRlLWJhbmQgc2lkZS1iYW5kLTY0ayBvZnMtZGVsdGEgc2hhbGxvdyBkZWVwZW4tc2luY2UgZGVlcGVuLW5vdCBkZWVwZW4tcmVsYXRpdmUgbm8tcHJvZ3Jlc3MgaW5jbHVkZS10YWcgbXVsdGlfYWNrX2RldGFpbGVkIGFsbG93LXRpcC1zaGExLWluLXdhbnQgYWxsb3ctcmVhY2hhYmxlLXNoYTEtaW4td2FudCBuby1kb25lIHN5bXJlZj1IRUFEOnJlZnMvaGVhZHMvbWFzdGVyIGZpbHRlciBvYmplY3QtZm9ybWF0PXNoYTEgYWdlbnQ9Z2l0L2dpdGh1Yi1nNzhiNDUyNDEzZThiCjAwM2ZlOGQzZmZhYjU1Mjg5NWMxOWI5ZmNmN2FhMjY0ZDI3N2NkZTMzODgxIHJlZnMvaGVhZHMvYnJhbmNoCjAwM2Y2ZWNmMGVmMmMyZGZmYjc5NjAzM2U1YTAyMjE5YWY4NmVjNjU4NGU1IHJlZnMvaGVhZHMvbWFzdGVyCjAwM2ViOGU0NzFmNThiY2JjYTYzYjA3YmRhMjBlNDI4MTkwNDA5YzJkYjQ3IHJlZnMvcHVsbC8xL2hlYWQKMDAzZTk2MzJmMDI4MzNiMmY5NjEzYWZiNWU3NTY4MjEzMmIwYjIyZTRhMzEgcmVmcy9wdWxsLzIvaGVhZAowMDNmYzM3ZjU4YTEzMGNhNTU1ZTQyZmY5NmEwNzFjYjljY2IzZjQzNzUwNCByZWZzL3B1bGwvMi9tZXJnZQowMDAw`) + require.NoError(t, err) + + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err = w.Write(bts) + assert.NoError(t, err, "failed to write response") + })) + defer s.Close() + + err = gp.CheckPermissions(context.Background(), s.URL) + require.NoError(t, err, "repo must be accessible") +} + +func TestGitProvider_CheckPermissions_NoRefs(t *testing.T) { + user := "user" + pass := "pass" + + config := Config{ + Username: user, + Token: pass, + } + gp := NewGitProvider(config) + + bts, err := base64.StdEncoding.DecodeString(`MDAxZSMgc2VydmljZT1naXQtdXBsb2FkLXBhY2sKMDAwMDAwZGUwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIGNhcGFiaWxpdGllc157fQAgaW5jbHVkZS10YWcgbXVsdGlfYWNrX2RldGFpbGVkIG11bHRpX2FjayBvZnMtZGVsdGEgc2lkZS1iYW5kIHNpZGUtYmFuZC02NGsgdGhpbi1wYWNrIG5vLXByb2dyZXNzIHNoYWxsb3cgbm8tZG9uZSBhZ2VudD1KR2l0L3Y1LjkuMC4yMDIwMDkwODA1MDEtci00MS1nNWQ5MjVlY2JiCjAwMDA=`) + require.NoError(t, err) + + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err = w.Write(bts) + assert.NoError(t, err, "failed to write response") + })) + defer s.Close() + + mockLogger := platform.NewLoggerMock() + + // v2 implementation returns nil for empty repos (they are technically accessible, just empty) + // This is different from v1 which logged an error + err = gp.CheckPermissions(ctrl.LoggerInto(context.Background(), mockLogger), s.URL) + require.NoError(t, err, "v2 considers empty repos accessible") +} + +func TestGitProvider_CreateChildBranch(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + parent string + child string + wantErr require.ErrorAssertionFunc + }{ + { + name: "should create child branch successfully", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + // Create initial commit on master branch + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test content") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial commit", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + // Create a parent branch and check it out so it exists as a proper reference + err = w.Checkout(&gogit.CheckoutOptions{ + Branch: plumbing.NewBranchReferenceName("parent-branch"), + Create: true, + }) + require.NoError(t, err) + + return dir + }, + parent: "parent-branch", + child: "child-branch", + wantErr: require.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.CreateChildBranch(context.Background(), dir, tt.parent, tt.child) + tt.wantErr(t, err) + }) + } +} + +func TestGitProvider_RemoveBranch(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + branch string + wantErr require.ErrorAssertionFunc + }{ + { + name: "should remove branch successfully", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + // Create initial commit + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test content") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial commit", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + // Create a new branch + err = w.Checkout(&gogit.CheckoutOptions{ + Branch: plumbing.NewBranchReferenceName("test-branch"), + Create: true, + }) + require.NoError(t, err) + + // Checkout back to master so we can delete test-branch + err = w.Checkout(&gogit.CheckoutOptions{ + Branch: plumbing.NewBranchReferenceName("master"), + }) + require.NoError(t, err) + + return dir + }, + branch: "test-branch", + wantErr: require.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.RemoveBranch(context.Background(), dir, tt.branch) + tt.wantErr(t, err) + }) + } +} + +func TestGitProvider_CommitChanges(t *testing.T) { + tests := []struct { + name string + ops []CommitOps + initRepo func(t *testing.T) string + wantErr require.ErrorAssertionFunc + checkRepo func(t *testing.T, dir string) + }{ + { + name: "should commit changes successfully", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + _, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + _, err = os.Create(path.Join(dir, "config.yaml")) + require.NoError(t, err) + + return dir + }, + wantErr: require.NoError, + checkRepo: func(t *testing.T, dir string) { + r, err := gogit.PlainOpen(dir) + require.NoError(t, err) + + commits, err := r.CommitObjects() + require.NoError(t, err) + + count := 0 + _ = commits.ForEach(func(*object.Commit) error { + count++ + + return nil + }) + + require.Equalf(t, 1, count, "expected 1 commits, got %d", count) + }, + }, + { + name: "skip commit if no changes", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + _, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + return dir + }, + wantErr: require.NoError, + checkRepo: func(t *testing.T, dir string) { + r, err := gogit.PlainOpen(dir) + require.NoError(t, err) + + commits, err := r.CommitObjects() + require.NoError(t, err) + + count := 0 + _ = commits.ForEach(func(*object.Commit) error { + count++ + + return nil + }) + + require.Equalf(t, 0, count, "expected 0 commits, got %d", count) + }, + }, + { + name: "should create empty commit", + ops: []CommitOps{ + CommitAllowEmpty(), + }, + initRepo: func(t *testing.T) string { + dir := t.TempDir() + _, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + return dir + }, + wantErr: require.NoError, + checkRepo: func(t *testing.T, dir string) { + r, err := gogit.PlainOpen(dir) + require.NoError(t, err) + + commits, err := r.CommitObjects() + require.NoError(t, err) + + count := 0 + _ = commits.ForEach(func(*object.Commit) error { + count++ + + return nil + }) + + require.Equalf(t, 1, count, "expected 1 commits, got %d", count) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.Commit(context.Background(), dir, "test commit message", tt.ops...) + tt.wantErr(t, err) + tt.checkRepo(t, dir) + }) + } +} + +func TestGitProvider_AddRemoteLink(t *testing.T) { + tests := []struct { + name string + remoteUrl string + initRepo func(t *testing.T) string + wantErr require.ErrorAssertionFunc + checkRepo func(t *testing.T, dir string) + }{ + { + name: "should add remote link successfully", + remoteUrl: "git@host:32/app.git", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + _, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + return dir + }, + wantErr: require.NoError, + checkRepo: func(t *testing.T, dir string) { + r, err := gogit.PlainOpen(dir) + require.NoError(t, err) + + remote, err := r.Remote("origin") + require.NoError(t, err) + + require.Equal(t, "origin", remote.Config().Name) + require.Len(t, remote.Config().URLs, 1) + require.Equal(t, "git@host:32/app.git", remote.Config().URLs[0]) + }, + }, + { + name: "empty git dir", + remoteUrl: "git@host:32/app.git", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + + return dir + }, + wantErr: require.Error, + checkRepo: func(t *testing.T, dir string) {}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.AddRemoteLink(context.Background(), dir, tt.remoteUrl) + tt.wantErr(t, err) + tt.checkRepo(t, dir) + }) + } +} + +func TestGitProvider_CheckReference(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + from string + wantErr require.ErrorAssertionFunc + }{ + { + name: "should return nil for empty reference", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + _, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + return dir + }, + from: "", + wantErr: require.NoError, + }, + { + name: "should find existing branch reference", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + // Create initial commit + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test content") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial commit", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + // Create and checkout a new branch + err = w.Checkout(&gogit.CheckoutOptions{ + Branch: plumbing.NewBranchReferenceName("test-branch"), + Create: true, + }) + require.NoError(t, err) + + return dir + }, + from: "test-branch", + wantErr: require.NoError, + }, + { + name: "should find existing commit reference", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + // Create initial commit + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test content") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + commit, err := w.Commit("initial commit", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + // Store the commit hash for the test + t.Logf("Created commit with hash: %s", commit.String()) + + return dir + }, + from: "", // Will be set dynamically + wantErr: require.NoError, + }, + { + name: "should return error for non-existent reference", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + _, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + return dir + }, + from: "non-existent", + wantErr: require.Error, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + // For the commit reference test, we need to get the actual commit hash + if tt.name == "should find existing commit reference" { + r, err := gogit.PlainOpen(dir) + require.NoError(t, err) + + ref, err := r.Head() + require.NoError(t, err) + + tt.from = ref.Hash().String() + t.Logf("Using commit hash: %s", tt.from) + } + + err := gp.CheckReference(context.Background(), dir, tt.from) + tt.wantErr(t, err) + }) + } +} + +func TestGitProvider_getAuth(t *testing.T) { + tests := []struct { + name string + config Config + wantErr bool + wantNil bool + }{ + { + name: "token authentication", + config: Config{ + Token: "test-token", + Username: "test-user", + }, + wantErr: false, + wantNil: false, + }, + { + name: "no authentication", + config: Config{ + Token: "", + SSHKey: "", + }, + wantErr: false, + wantNil: true, + }, + { + name: "invalid SSH key", + config: Config{ + SSHKey: "invalid-key-format", + }, + wantErr: true, + wantNil: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(tt.config) + auth, err := gp.getAuth() + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + + if tt.wantNil { + assert.Nil(t, auth) + } else { + assert.NotNil(t, auth) + } + }) + } +} + +func TestGitProvider_getTokenAuth(t *testing.T) { + tests := []struct { + name string + gitProvider string + username string + token string + wantUsername string + wantPassword string + }{ + { + name: "GitHub token format", + gitProvider: "github", + username: "test-user", + token: "ghp_test_token", + wantUsername: "test-user", + wantPassword: "ghp_test_token", + }, + { + name: "GitLab token format (oauth2)", + gitProvider: "gitlab", + username: "test-user", + token: "glpat-test-token", + wantUsername: "oauth2", + wantPassword: "glpat-test-token", + }, + { + name: "Bitbucket token format", + gitProvider: "bitbucket", + username: "test-user", + token: "test-token", + wantUsername: "test-user", + wantPassword: "test-token", + }, + { + name: "default format (unknown provider)", + gitProvider: "unknown", + username: "test-user", + token: "test-token", + wantUsername: "test-user", + wantPassword: "test-token", + }, + { + name: "gerrit provider uses default", + gitProvider: "gerrit", + username: "test-user", + token: "test-token", + wantUsername: "test-user", + wantPassword: "test-token", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{ + GitProvider: tt.gitProvider, + Username: tt.username, + Token: tt.token, + }) + + auth := gp.getTokenAuth() + require.NotNil(t, auth) + + // Type assert to *githttp.BasicAuth to access Username and Password + basicAuth, ok := auth.(*githttp.BasicAuth) + require.True(t, ok, "auth should be *githttp.BasicAuth") + assert.Equal(t, tt.wantUsername, basicAuth.Username) + assert.Equal(t, tt.wantPassword, basicAuth.Password) + }) + } +} + +func TestGitProvider_NewGitProvider(t *testing.T) { + tests := []struct { + name string + config Config + wantSSHUser string + wantSSHPort int32 + wantGitProvider string + }{ + { + name: "default SSH user and port", + config: Config{ + GitProvider: "github", + }, + wantSSHUser: "git", + wantSSHPort: 22, + wantGitProvider: "github", + }, + { + name: "custom SSH user and port", + config: Config{ + GitProvider: "gitlab", + SSHUser: "custom-user", + SSHPort: 2222, + }, + wantSSHUser: "custom-user", + wantSSHPort: 2222, + wantGitProvider: "gitlab", + }, + { + name: "partial custom config (only user)", + config: Config{ + GitProvider: "bitbucket", + SSHUser: "admin", + }, + wantSSHUser: "admin", + wantSSHPort: 22, + wantGitProvider: "bitbucket", + }, + { + name: "partial custom config (only port)", + config: Config{ + GitProvider: "gerrit", + SSHPort: 29418, + }, + wantSSHUser: "git", + wantSSHPort: 29418, + wantGitProvider: "gerrit", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(tt.config) + + assert.Equal(t, tt.wantSSHUser, gp.config.SSHUser) + assert.Equal(t, tt.wantSSHPort, gp.config.SSHPort) + assert.Equal(t, tt.wantGitProvider, gp.config.GitProvider) + }) + } +} + +func TestGitProvider_Init(t *testing.T) { + tests := []struct { + name string + setupDir func(t *testing.T) string + wantErr bool + checkRepo func(t *testing.T, dir string) + }{ + { + name: "initialize new repository", + setupDir: func(t *testing.T) string { + return t.TempDir() + }, + wantErr: false, + checkRepo: func(t *testing.T, dir string) { + r, err := gogit.PlainOpen(dir) + require.NoError(t, err) + assert.NotNil(t, r) + }, + }, + { + name: "initialize already initialized repository", + setupDir: func(t *testing.T) string { + dir := t.TempDir() + _, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + return dir + }, + wantErr: true, + checkRepo: func(t *testing.T, dir string) { + // Repository should still be valid even though init failed + r, err := gogit.PlainOpen(dir) + require.NoError(t, err) + assert.NotNil(t, r) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.setupDir(t) + + err := gp.Init(context.Background(), dir) + + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + + tt.checkRepo(t, dir) + }) + } +} + +func TestGitProvider_GetCurrentBranchName(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + wantBranch string + wantErr bool + }{ + { + name: "get current branch name", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + // Create initial commit + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + return dir + }, + wantBranch: "master", + wantErr: false, + }, + { + name: "repository not found", + initRepo: func(t *testing.T) string { + return t.TempDir() + }, + wantBranch: "", + wantErr: true, + }, + { + name: "get branch after checkout", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + // Create and checkout a new branch + err = w.Checkout(&gogit.CheckoutOptions{ + Branch: plumbing.NewBranchReferenceName("feature-branch"), + Create: true, + }) + require.NoError(t, err) + + return dir + }, + wantBranch: "feature-branch", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + branch, err := gp.GetCurrentBranchName(context.Background(), dir) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, tt.wantBranch, branch) + }) + } +} + +func TestGitProvider_CommitExists(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) (string, string) + commitHash string + wantExists bool + wantErr bool + }{ + { + name: "commit exists", + initRepo: func(t *testing.T) (string, string) { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + hash, err := w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + return dir, hash.String() + }, + wantExists: true, + wantErr: false, + }, + { + name: "commit does not exist", + initRepo: func(t *testing.T) (string, string) { + dir := t.TempDir() + _, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + // Return a valid-looking but non-existent commit hash + return dir, "0000000000000000000000000000000000000000" + }, + wantExists: false, + wantErr: false, + }, + { + name: "invalid hash - treated as not found", + initRepo: func(t *testing.T) (string, string) { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + // Create a commit so the repo has valid objects + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + // Invalid hash format is treated as not found, not an error + return dir, "invalid-hash" + }, + wantExists: false, + wantErr: false, + }, + { + name: "repository error", + initRepo: func(t *testing.T) (string, string) { + // Return a non-git directory + return t.TempDir(), "0000000000000000000000000000000000000000" + }, + wantExists: false, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir, hash := tt.initRepo(t) + + exists, err := gp.CommitExists(context.Background(), dir, hash) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, tt.wantExists, exists) + }) + } +} + +func TestBranchExists(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + branchName string + wantExists bool + wantErr bool + }{ + { + name: "branch exists", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + // Create test-branch + err = w.Checkout(&gogit.CheckoutOptions{ + Branch: plumbing.NewBranchReferenceName("test-branch"), + Create: true, + }) + require.NoError(t, err) + + return dir + }, + branchName: "test-branch", + wantExists: true, + wantErr: false, + }, + { + name: "branch does not exist", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + return dir + }, + branchName: "non-existent-branch", + wantExists: false, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := tt.initRepo(t) + r, err := gogit.PlainOpen(dir) + require.NoError(t, err) + + branches, err := r.Branches() + require.NoError(t, err) + + exists, err := branchExists(tt.branchName, branches) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, tt.wantExists, exists) + }) + } +} + +func TestResolveReference(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) (string, string) + ref string + wantErr bool + }{ + { + name: "empty ref uses HEAD", + initRepo: func(t *testing.T) (string, string) { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + return dir, "" + }, + ref: "", + wantErr: false, + }, + { + name: "branch reference resolution", + initRepo: func(t *testing.T) (string, string) { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + return dir, "master" + }, + ref: "master", + wantErr: false, + }, + { + name: "commit hash resolution", + initRepo: func(t *testing.T) (string, string) { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + hash, err := w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + return dir, hash.String() + }, + wantErr: false, + }, + { + name: "invalid reference", + initRepo: func(t *testing.T) (string, string) { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + return dir, "invalid-ref-12345" + }, + ref: "invalid-ref-12345", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir, ref := tt.initRepo(t) + r, err := gogit.PlainOpen(dir) + require.NoError(t, err) + + // Use the ref from initRepo if one was provided + testRef := tt.ref + if testRef == "" && ref != "" { + testRef = ref + } + + hash, err := resolveReference(r, testRef) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + assert.NotNil(t, hash) + }) + } +} + +func TestGitProvider_Checkout_LocalMode(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + branchName string + remote bool + wantErr bool + }{ + { + name: "checkout local branch (remote=false)", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + w, err := r.Worktree() + require.NoError(t, err) + + // Create initial commit + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + return dir + }, + branchName: "test-branch", + remote: false, + wantErr: false, + }, + { + name: "checkout on non-existent directory", + initRepo: func(t *testing.T) string { + return t.TempDir() // Empty directory, not a git repo + }, + branchName: "test-branch", + remote: false, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.Checkout(context.Background(), dir, tt.branchName, tt.remote) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + + // Verify the branch was checked out + r, err := gogit.PlainOpen(dir) + require.NoError(t, err) + + head, err := r.Head() + require.NoError(t, err) + + assert.Equal(t, tt.branchName, head.Name().Short()) + }) + } +} + +func TestGitProvider_CreateRemoteBranch_Errors(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + branchName string + fromRef string + wantErr bool + }{ + { + name: "repository not found", + initRepo: func(t *testing.T) string { + return t.TempDir() + }, + branchName: "new-branch", + fromRef: "master", + wantErr: true, + }, + { + name: "invalid fromRef", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + return dir + }, + branchName: "new-branch", + fromRef: "non-existent-ref", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.CreateRemoteBranch(context.Background(), dir, tt.branchName, tt.fromRef) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestGitProvider_CreateRemoteTag_Errors(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + tagName string + branchName string + wantErr bool + }{ + { + name: "repository not found", + initRepo: func(t *testing.T) string { + return t.TempDir() + }, + tagName: "v1.0.0", + branchName: "master", + wantErr: true, + }, + { + name: "branch not found", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + _, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + return dir + }, + tagName: "v1.0.0", + branchName: "non-existent-branch", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.CreateRemoteTag(context.Background(), dir, tt.tagName, tt.branchName) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestGitProvider_Clone_Errors(t *testing.T) { + tests := []struct { + name string + repoURL string + wantErr bool + }{ + { + name: "clone with empty URL", + repoURL: "", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + destination := t.TempDir() + + err := gp.Clone(context.Background(), tt.repoURL, destination) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestGitProvider_Commit_ErrorCases(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + message string + wantErr bool + }{ + { + name: "commit with invalid directory", + initRepo: func(t *testing.T) string { + return t.TempDir() + }, + message: "test commit", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.Commit(context.Background(), dir, tt.message) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestGitProvider_Push_ErrorCases(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + refspec string + wantErr bool + }{ + { + name: "push with invalid directory", + initRepo: func(t *testing.T) string { + return t.TempDir() + }, + refspec: RefSpecPushAllBranches, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.Push(context.Background(), dir, tt.refspec) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestGitProvider_CheckoutRemoteBranch_Errors(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + branchName string + wantErr bool + }{ + { + name: "invalid directory", + initRepo: func(t *testing.T) string { + return t.TempDir() + }, + branchName: "feature-branch", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.CheckoutRemoteBranch(context.Background(), dir, tt.branchName) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestGitProvider_CreateChildBranch_Errors(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + parentName string + childName string + wantErr bool + }{ + { + name: "invalid directory", + initRepo: func(t *testing.T) string { + return t.TempDir() + }, + parentName: "parent", + childName: "child", + wantErr: true, + }, + { + name: "non-existent parent", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + return dir + }, + parentName: "non-existent-parent", + childName: "child", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.CreateChildBranch(context.Background(), dir, tt.parentName, tt.childName) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestGitProvider_RemoveBranch_Errors(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + branchName string + wantErr bool + }{ + { + name: "invalid directory", + initRepo: func(t *testing.T) string { + return t.TempDir() + }, + branchName: "test-branch", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.RemoveBranch(context.Background(), dir, tt.branchName) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestGitProvider_Clone_Basic(t *testing.T) { + tests := []struct { + name string + setupEnv func(t *testing.T) (string, string) + wantErr bool + }{ + { + name: "invalid URL", + setupEnv: func(t *testing.T) (string, string) { + targetDir := t.TempDir() + return targetDir, "invalid-url://invalid" + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + targetDir, repoURL := tt.setupEnv(t) + + err := gp.Clone(context.Background(), repoURL, targetDir) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + + // Verify the clone succeeded + _, err = gogit.PlainOpen(targetDir) + require.NoError(t, err) + }) + } +} + +func TestGitProvider_Push_NoRemote(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + refspec string + wantErr bool + }{ + { + name: "push without remote configured", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + return dir + }, + refspec: RefSpecPushAllBranches, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.Push(context.Background(), dir, tt.refspec) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestGitProvider_Fetch_NoRemote(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + branchName string + wantErr bool + }{ + { + name: "fetch without remote configured", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + return dir + }, + branchName: "", + wantErr: true, + }, + { + name: "fetch specific branch without remote", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + return dir + }, + branchName: "master", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.Fetch(context.Background(), dir, tt.branchName) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestGitProvider_CheckoutRemoteBranch_NoRemote(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + branchName string + wantErr bool + }{ + { + name: "checkout remote branch without remote", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + return dir + }, + branchName: "feature-branch", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.CheckoutRemoteBranch(context.Background(), dir, tt.branchName) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestGitProvider_CreateRemoteTag_NoRemote(t *testing.T) { + tests := []struct { + name string + initRepo func(t *testing.T) string + tagName string + branchName string + wantErr bool + }{ + { + name: "create tag without remote", + initRepo: func(t *testing.T) string { + dir := t.TempDir() + r, err := gogit.PlainInit(dir, false) + require.NoError(t, err) + + w, err := r.Worktree() + require.NoError(t, err) + + f, err := os.Create(path.Join(dir, "test.txt")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + _, err = w.Add("test.txt") + require.NoError(t, err) + + _, err = w.Commit("initial", &gogit.CommitOptions{ + Author: &object.Signature{ + Name: "test", + Email: "test@example.com", + When: time.Now(), + }, + }) + require.NoError(t, err) + + return dir + }, + tagName: "v1.0.0", + branchName: "master", + wantErr: true, // Will fail on push + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gp := NewGitProvider(Config{}) + dir := tt.initRepo(t) + + err := gp.CreateRemoteTag(context.Background(), dir, tt.tagName, tt.branchName) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} diff --git a/pkg/git/v2/git.go b/pkg/git/v2/git.go deleted file mode 100644 index 29be0276..00000000 --- a/pkg/git/v2/git.go +++ /dev/null @@ -1,59 +0,0 @@ -package v2 - -import "context" - -// Git interface provides methods for working with git using v2 GitProvider. -// This interface uses context-aware methods and handles authentication via Config. -type Git interface { - // Clone clones a repository to the specified destination with full history. - Clone(ctx context.Context, repoURL, destination string) error - - // Commit commits changes in the working directory. - Commit(ctx context.Context, directory, message string, ops ...CommitOps) error - - // Push pushes changes to the remote repository. - // refspecs: optional refspecs (e.g., RefSpecPushAllBranches, RefSpecPushAllTags). - Push(ctx context.Context, directory string, refspecs ...string) error - - // Checkout checks out a branch in the repository. - // If remote is true, fetches from remote first and only creates local branch if it doesn't exist remotely. - Checkout(ctx context.Context, directory, branchName string, remote bool) error - - // CreateRemoteBranch creates a new branch from a reference and pushes it to remote. - // fromRef: branch name or commit hash to create from (empty string means HEAD). - CreateRemoteBranch(ctx context.Context, directory, branchName, fromRef string) error - - // GetCurrentBranchName returns the name of the current branch. - GetCurrentBranchName(ctx context.Context, directory string) (string, error) - - // CheckPermissions checks if the repository is accessible with current credentials. - CheckPermissions(ctx context.Context, repoURL string) error - - // CheckReference checks if a reference (branch or commit) exists in the repository. - CheckReference(ctx context.Context, directory, refName string) error - - // RemoveBranch removes a local branch. - RemoveBranch(ctx context.Context, directory, branchName string) error - - // CreateChildBranch creates a new branch from an existing branch. - CreateChildBranch(ctx context.Context, directory, parentBranch, newBranch string) error - - // Init initializes a new git repository. - Init(ctx context.Context, directory string) error - - // Fetch fetches changes from the remote repository. - // branchName: specific branch to fetch (empty string fetches all). - Fetch(ctx context.Context, directory, branchName string) error - - // AddRemoteLink adds or updates the remote origin URL. - AddRemoteLink(ctx context.Context, directory, remoteURL string) error - - // CommitExists checks if a commit with the given hash exists in the repository. - CommitExists(ctx context.Context, directory, hash string) (bool, error) - - // CheckoutRemoteBranch fetches from remote and checks out the specified branch. - CheckoutRemoteBranch(ctx context.Context, directory, branchName string) error - - // CreateRemoteTag creates a tag from a branch and pushes it to the remote repository. - CreateRemoteTag(ctx context.Context, directory, branchName, tagName string) error -} diff --git a/pkg/git/v2/mocks/git_mock.go b/pkg/git/v2/mocks/git_mock.go deleted file mode 100644 index fd838577..00000000 --- a/pkg/git/v2/mocks/git_mock.go +++ /dev/null @@ -1,855 +0,0 @@ -// Code generated by mockery. DO NOT EDIT. - -package mocks - -import ( - context "context" - - v2 "github.com/epam/edp-codebase-operator/v2/pkg/git/v2" - mock "github.com/stretchr/testify/mock" -) - -// MockGit is an autogenerated mock type for the Git type -type MockGit struct { - mock.Mock -} - -type MockGit_Expecter struct { - mock *mock.Mock -} - -func (_m *MockGit) EXPECT() *MockGit_Expecter { - return &MockGit_Expecter{mock: &_m.Mock} -} - -// AddRemoteLink provides a mock function with given fields: ctx, directory, remoteURL -func (_m *MockGit) AddRemoteLink(ctx context.Context, directory string, remoteURL string) error { - ret := _m.Called(ctx, directory, remoteURL) - - if len(ret) == 0 { - panic("no return value specified for AddRemoteLink") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, directory, remoteURL) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_AddRemoteLink_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddRemoteLink' -type MockGit_AddRemoteLink_Call struct { - *mock.Call -} - -// AddRemoteLink is a helper method to define mock.On call -// - ctx context.Context -// - directory string -// - remoteURL string -func (_e *MockGit_Expecter) AddRemoteLink(ctx interface{}, directory interface{}, remoteURL interface{}) *MockGit_AddRemoteLink_Call { - return &MockGit_AddRemoteLink_Call{Call: _e.mock.On("AddRemoteLink", ctx, directory, remoteURL)} -} - -func (_c *MockGit_AddRemoteLink_Call) Run(run func(ctx context.Context, directory string, remoteURL string)) *MockGit_AddRemoteLink_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string)) - }) - return _c -} - -func (_c *MockGit_AddRemoteLink_Call) Return(_a0 error) *MockGit_AddRemoteLink_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_AddRemoteLink_Call) RunAndReturn(run func(context.Context, string, string) error) *MockGit_AddRemoteLink_Call { - _c.Call.Return(run) - return _c -} - -// CheckPermissions provides a mock function with given fields: ctx, repoURL -func (_m *MockGit) CheckPermissions(ctx context.Context, repoURL string) error { - ret := _m.Called(ctx, repoURL) - - if len(ret) == 0 { - panic("no return value specified for CheckPermissions") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, repoURL) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_CheckPermissions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckPermissions' -type MockGit_CheckPermissions_Call struct { - *mock.Call -} - -// CheckPermissions is a helper method to define mock.On call -// - ctx context.Context -// - repoURL string -func (_e *MockGit_Expecter) CheckPermissions(ctx interface{}, repoURL interface{}) *MockGit_CheckPermissions_Call { - return &MockGit_CheckPermissions_Call{Call: _e.mock.On("CheckPermissions", ctx, repoURL)} -} - -func (_c *MockGit_CheckPermissions_Call) Run(run func(ctx context.Context, repoURL string)) *MockGit_CheckPermissions_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) - }) - return _c -} - -func (_c *MockGit_CheckPermissions_Call) Return(_a0 error) *MockGit_CheckPermissions_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_CheckPermissions_Call) RunAndReturn(run func(context.Context, string) error) *MockGit_CheckPermissions_Call { - _c.Call.Return(run) - return _c -} - -// CheckReference provides a mock function with given fields: ctx, directory, refName -func (_m *MockGit) CheckReference(ctx context.Context, directory string, refName string) error { - ret := _m.Called(ctx, directory, refName) - - if len(ret) == 0 { - panic("no return value specified for CheckReference") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, directory, refName) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_CheckReference_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckReference' -type MockGit_CheckReference_Call struct { - *mock.Call -} - -// CheckReference is a helper method to define mock.On call -// - ctx context.Context -// - directory string -// - refName string -func (_e *MockGit_Expecter) CheckReference(ctx interface{}, directory interface{}, refName interface{}) *MockGit_CheckReference_Call { - return &MockGit_CheckReference_Call{Call: _e.mock.On("CheckReference", ctx, directory, refName)} -} - -func (_c *MockGit_CheckReference_Call) Run(run func(ctx context.Context, directory string, refName string)) *MockGit_CheckReference_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string)) - }) - return _c -} - -func (_c *MockGit_CheckReference_Call) Return(_a0 error) *MockGit_CheckReference_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_CheckReference_Call) RunAndReturn(run func(context.Context, string, string) error) *MockGit_CheckReference_Call { - _c.Call.Return(run) - return _c -} - -// Checkout provides a mock function with given fields: ctx, directory, branchName, remote -func (_m *MockGit) Checkout(ctx context.Context, directory string, branchName string, remote bool) error { - ret := _m.Called(ctx, directory, branchName, remote) - - if len(ret) == 0 { - panic("no return value specified for Checkout") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, bool) error); ok { - r0 = rf(ctx, directory, branchName, remote) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_Checkout_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Checkout' -type MockGit_Checkout_Call struct { - *mock.Call -} - -// Checkout is a helper method to define mock.On call -// - ctx context.Context -// - directory string -// - branchName string -// - remote bool -func (_e *MockGit_Expecter) Checkout(ctx interface{}, directory interface{}, branchName interface{}, remote interface{}) *MockGit_Checkout_Call { - return &MockGit_Checkout_Call{Call: _e.mock.On("Checkout", ctx, directory, branchName, remote)} -} - -func (_c *MockGit_Checkout_Call) Run(run func(ctx context.Context, directory string, branchName string, remote bool)) *MockGit_Checkout_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(bool)) - }) - return _c -} - -func (_c *MockGit_Checkout_Call) Return(_a0 error) *MockGit_Checkout_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_Checkout_Call) RunAndReturn(run func(context.Context, string, string, bool) error) *MockGit_Checkout_Call { - _c.Call.Return(run) - return _c -} - -// CheckoutRemoteBranch provides a mock function with given fields: ctx, directory, branchName -func (_m *MockGit) CheckoutRemoteBranch(ctx context.Context, directory string, branchName string) error { - ret := _m.Called(ctx, directory, branchName) - - if len(ret) == 0 { - panic("no return value specified for CheckoutRemoteBranch") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, directory, branchName) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_CheckoutRemoteBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckoutRemoteBranch' -type MockGit_CheckoutRemoteBranch_Call struct { - *mock.Call -} - -// CheckoutRemoteBranch is a helper method to define mock.On call -// - ctx context.Context -// - directory string -// - branchName string -func (_e *MockGit_Expecter) CheckoutRemoteBranch(ctx interface{}, directory interface{}, branchName interface{}) *MockGit_CheckoutRemoteBranch_Call { - return &MockGit_CheckoutRemoteBranch_Call{Call: _e.mock.On("CheckoutRemoteBranch", ctx, directory, branchName)} -} - -func (_c *MockGit_CheckoutRemoteBranch_Call) Run(run func(ctx context.Context, directory string, branchName string)) *MockGit_CheckoutRemoteBranch_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string)) - }) - return _c -} - -func (_c *MockGit_CheckoutRemoteBranch_Call) Return(_a0 error) *MockGit_CheckoutRemoteBranch_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_CheckoutRemoteBranch_Call) RunAndReturn(run func(context.Context, string, string) error) *MockGit_CheckoutRemoteBranch_Call { - _c.Call.Return(run) - return _c -} - -// Clone provides a mock function with given fields: ctx, repoURL, destination -func (_m *MockGit) Clone(ctx context.Context, repoURL string, destination string) error { - ret := _m.Called(ctx, repoURL, destination) - - if len(ret) == 0 { - panic("no return value specified for Clone") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, repoURL, destination) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_Clone_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Clone' -type MockGit_Clone_Call struct { - *mock.Call -} - -// Clone is a helper method to define mock.On call -// - ctx context.Context -// - repoURL string -// - destination string -func (_e *MockGit_Expecter) Clone(ctx interface{}, repoURL interface{}, destination interface{}) *MockGit_Clone_Call { - return &MockGit_Clone_Call{Call: _e.mock.On("Clone", ctx, repoURL, destination)} -} - -func (_c *MockGit_Clone_Call) Run(run func(ctx context.Context, repoURL string, destination string)) *MockGit_Clone_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string)) - }) - return _c -} - -func (_c *MockGit_Clone_Call) Return(_a0 error) *MockGit_Clone_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_Clone_Call) RunAndReturn(run func(context.Context, string, string) error) *MockGit_Clone_Call { - _c.Call.Return(run) - return _c -} - -// Commit provides a mock function with given fields: ctx, directory, message, ops -func (_m *MockGit) Commit(ctx context.Context, directory string, message string, ops ...v2.CommitOps) error { - _va := make([]interface{}, len(ops)) - for _i := range ops { - _va[_i] = ops[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx, directory, message) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for Commit") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, ...v2.CommitOps) error); ok { - r0 = rf(ctx, directory, message, ops...) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_Commit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Commit' -type MockGit_Commit_Call struct { - *mock.Call -} - -// Commit is a helper method to define mock.On call -// - ctx context.Context -// - directory string -// - message string -// - ops ...v2.CommitOps -func (_e *MockGit_Expecter) Commit(ctx interface{}, directory interface{}, message interface{}, ops ...interface{}) *MockGit_Commit_Call { - return &MockGit_Commit_Call{Call: _e.mock.On("Commit", - append([]interface{}{ctx, directory, message}, ops...)...)} -} - -func (_c *MockGit_Commit_Call) Run(run func(ctx context.Context, directory string, message string, ops ...v2.CommitOps)) *MockGit_Commit_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]v2.CommitOps, len(args)-3) - for i, a := range args[3:] { - if a != nil { - variadicArgs[i] = a.(v2.CommitOps) - } - } - run(args[0].(context.Context), args[1].(string), args[2].(string), variadicArgs...) - }) - return _c -} - -func (_c *MockGit_Commit_Call) Return(_a0 error) *MockGit_Commit_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_Commit_Call) RunAndReturn(run func(context.Context, string, string, ...v2.CommitOps) error) *MockGit_Commit_Call { - _c.Call.Return(run) - return _c -} - -// CommitExists provides a mock function with given fields: ctx, directory, hash -func (_m *MockGit) CommitExists(ctx context.Context, directory string, hash string) (bool, error) { - ret := _m.Called(ctx, directory, hash) - - if len(ret) == 0 { - panic("no return value specified for CommitExists") - } - - var r0 bool - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok { - return rf(ctx, directory, hash) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string) bool); ok { - r0 = rf(ctx, directory, hash) - } else { - r0 = ret.Get(0).(bool) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = rf(ctx, directory, hash) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockGit_CommitExists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CommitExists' -type MockGit_CommitExists_Call struct { - *mock.Call -} - -// CommitExists is a helper method to define mock.On call -// - ctx context.Context -// - directory string -// - hash string -func (_e *MockGit_Expecter) CommitExists(ctx interface{}, directory interface{}, hash interface{}) *MockGit_CommitExists_Call { - return &MockGit_CommitExists_Call{Call: _e.mock.On("CommitExists", ctx, directory, hash)} -} - -func (_c *MockGit_CommitExists_Call) Run(run func(ctx context.Context, directory string, hash string)) *MockGit_CommitExists_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string)) - }) - return _c -} - -func (_c *MockGit_CommitExists_Call) Return(_a0 bool, _a1 error) *MockGit_CommitExists_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockGit_CommitExists_Call) RunAndReturn(run func(context.Context, string, string) (bool, error)) *MockGit_CommitExists_Call { - _c.Call.Return(run) - return _c -} - -// CreateChildBranch provides a mock function with given fields: ctx, directory, parentBranch, newBranch -func (_m *MockGit) CreateChildBranch(ctx context.Context, directory string, parentBranch string, newBranch string) error { - ret := _m.Called(ctx, directory, parentBranch, newBranch) - - if len(ret) == 0 { - panic("no return value specified for CreateChildBranch") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { - r0 = rf(ctx, directory, parentBranch, newBranch) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_CreateChildBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateChildBranch' -type MockGit_CreateChildBranch_Call struct { - *mock.Call -} - -// CreateChildBranch is a helper method to define mock.On call -// - ctx context.Context -// - directory string -// - parentBranch string -// - newBranch string -func (_e *MockGit_Expecter) CreateChildBranch(ctx interface{}, directory interface{}, parentBranch interface{}, newBranch interface{}) *MockGit_CreateChildBranch_Call { - return &MockGit_CreateChildBranch_Call{Call: _e.mock.On("CreateChildBranch", ctx, directory, parentBranch, newBranch)} -} - -func (_c *MockGit_CreateChildBranch_Call) Run(run func(ctx context.Context, directory string, parentBranch string, newBranch string)) *MockGit_CreateChildBranch_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string)) - }) - return _c -} - -func (_c *MockGit_CreateChildBranch_Call) Return(_a0 error) *MockGit_CreateChildBranch_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_CreateChildBranch_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockGit_CreateChildBranch_Call { - _c.Call.Return(run) - return _c -} - -// CreateRemoteBranch provides a mock function with given fields: ctx, directory, branchName, fromRef -func (_m *MockGit) CreateRemoteBranch(ctx context.Context, directory string, branchName string, fromRef string) error { - ret := _m.Called(ctx, directory, branchName, fromRef) - - if len(ret) == 0 { - panic("no return value specified for CreateRemoteBranch") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { - r0 = rf(ctx, directory, branchName, fromRef) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_CreateRemoteBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateRemoteBranch' -type MockGit_CreateRemoteBranch_Call struct { - *mock.Call -} - -// CreateRemoteBranch is a helper method to define mock.On call -// - ctx context.Context -// - directory string -// - branchName string -// - fromRef string -func (_e *MockGit_Expecter) CreateRemoteBranch(ctx interface{}, directory interface{}, branchName interface{}, fromRef interface{}) *MockGit_CreateRemoteBranch_Call { - return &MockGit_CreateRemoteBranch_Call{Call: _e.mock.On("CreateRemoteBranch", ctx, directory, branchName, fromRef)} -} - -func (_c *MockGit_CreateRemoteBranch_Call) Run(run func(ctx context.Context, directory string, branchName string, fromRef string)) *MockGit_CreateRemoteBranch_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string)) - }) - return _c -} - -func (_c *MockGit_CreateRemoteBranch_Call) Return(_a0 error) *MockGit_CreateRemoteBranch_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_CreateRemoteBranch_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockGit_CreateRemoteBranch_Call { - _c.Call.Return(run) - return _c -} - -// CreateRemoteTag provides a mock function with given fields: ctx, directory, branchName, tagName -func (_m *MockGit) CreateRemoteTag(ctx context.Context, directory string, branchName string, tagName string) error { - ret := _m.Called(ctx, directory, branchName, tagName) - - if len(ret) == 0 { - panic("no return value specified for CreateRemoteTag") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { - r0 = rf(ctx, directory, branchName, tagName) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_CreateRemoteTag_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateRemoteTag' -type MockGit_CreateRemoteTag_Call struct { - *mock.Call -} - -// CreateRemoteTag is a helper method to define mock.On call -// - ctx context.Context -// - directory string -// - branchName string -// - tagName string -func (_e *MockGit_Expecter) CreateRemoteTag(ctx interface{}, directory interface{}, branchName interface{}, tagName interface{}) *MockGit_CreateRemoteTag_Call { - return &MockGit_CreateRemoteTag_Call{Call: _e.mock.On("CreateRemoteTag", ctx, directory, branchName, tagName)} -} - -func (_c *MockGit_CreateRemoteTag_Call) Run(run func(ctx context.Context, directory string, branchName string, tagName string)) *MockGit_CreateRemoteTag_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string)) - }) - return _c -} - -func (_c *MockGit_CreateRemoteTag_Call) Return(_a0 error) *MockGit_CreateRemoteTag_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_CreateRemoteTag_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockGit_CreateRemoteTag_Call { - _c.Call.Return(run) - return _c -} - -// Fetch provides a mock function with given fields: ctx, directory, branchName -func (_m *MockGit) Fetch(ctx context.Context, directory string, branchName string) error { - ret := _m.Called(ctx, directory, branchName) - - if len(ret) == 0 { - panic("no return value specified for Fetch") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, directory, branchName) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_Fetch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Fetch' -type MockGit_Fetch_Call struct { - *mock.Call -} - -// Fetch is a helper method to define mock.On call -// - ctx context.Context -// - directory string -// - branchName string -func (_e *MockGit_Expecter) Fetch(ctx interface{}, directory interface{}, branchName interface{}) *MockGit_Fetch_Call { - return &MockGit_Fetch_Call{Call: _e.mock.On("Fetch", ctx, directory, branchName)} -} - -func (_c *MockGit_Fetch_Call) Run(run func(ctx context.Context, directory string, branchName string)) *MockGit_Fetch_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string)) - }) - return _c -} - -func (_c *MockGit_Fetch_Call) Return(_a0 error) *MockGit_Fetch_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_Fetch_Call) RunAndReturn(run func(context.Context, string, string) error) *MockGit_Fetch_Call { - _c.Call.Return(run) - return _c -} - -// GetCurrentBranchName provides a mock function with given fields: ctx, directory -func (_m *MockGit) GetCurrentBranchName(ctx context.Context, directory string) (string, error) { - ret := _m.Called(ctx, directory) - - if len(ret) == 0 { - panic("no return value specified for GetCurrentBranchName") - } - - var r0 string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (string, error)); ok { - return rf(ctx, directory) - } - if rf, ok := ret.Get(0).(func(context.Context, string) string); ok { - r0 = rf(ctx, directory) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, directory) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockGit_GetCurrentBranchName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCurrentBranchName' -type MockGit_GetCurrentBranchName_Call struct { - *mock.Call -} - -// GetCurrentBranchName is a helper method to define mock.On call -// - ctx context.Context -// - directory string -func (_e *MockGit_Expecter) GetCurrentBranchName(ctx interface{}, directory interface{}) *MockGit_GetCurrentBranchName_Call { - return &MockGit_GetCurrentBranchName_Call{Call: _e.mock.On("GetCurrentBranchName", ctx, directory)} -} - -func (_c *MockGit_GetCurrentBranchName_Call) Run(run func(ctx context.Context, directory string)) *MockGit_GetCurrentBranchName_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) - }) - return _c -} - -func (_c *MockGit_GetCurrentBranchName_Call) Return(_a0 string, _a1 error) *MockGit_GetCurrentBranchName_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockGit_GetCurrentBranchName_Call) RunAndReturn(run func(context.Context, string) (string, error)) *MockGit_GetCurrentBranchName_Call { - _c.Call.Return(run) - return _c -} - -// Init provides a mock function with given fields: ctx, directory -func (_m *MockGit) Init(ctx context.Context, directory string) error { - ret := _m.Called(ctx, directory) - - if len(ret) == 0 { - panic("no return value specified for Init") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, directory) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_Init_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Init' -type MockGit_Init_Call struct { - *mock.Call -} - -// Init is a helper method to define mock.On call -// - ctx context.Context -// - directory string -func (_e *MockGit_Expecter) Init(ctx interface{}, directory interface{}) *MockGit_Init_Call { - return &MockGit_Init_Call{Call: _e.mock.On("Init", ctx, directory)} -} - -func (_c *MockGit_Init_Call) Run(run func(ctx context.Context, directory string)) *MockGit_Init_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) - }) - return _c -} - -func (_c *MockGit_Init_Call) Return(_a0 error) *MockGit_Init_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_Init_Call) RunAndReturn(run func(context.Context, string) error) *MockGit_Init_Call { - _c.Call.Return(run) - return _c -} - -// Push provides a mock function with given fields: ctx, directory, refspecs -func (_m *MockGit) Push(ctx context.Context, directory string, refspecs ...string) error { - _va := make([]interface{}, len(refspecs)) - for _i := range refspecs { - _va[_i] = refspecs[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx, directory) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for Push") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, ...string) error); ok { - r0 = rf(ctx, directory, refspecs...) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_Push_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Push' -type MockGit_Push_Call struct { - *mock.Call -} - -// Push is a helper method to define mock.On call -// - ctx context.Context -// - directory string -// - refspecs ...string -func (_e *MockGit_Expecter) Push(ctx interface{}, directory interface{}, refspecs ...interface{}) *MockGit_Push_Call { - return &MockGit_Push_Call{Call: _e.mock.On("Push", - append([]interface{}{ctx, directory}, refspecs...)...)} -} - -func (_c *MockGit_Push_Call) Run(run func(ctx context.Context, directory string, refspecs ...string)) *MockGit_Push_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]string, len(args)-2) - for i, a := range args[2:] { - if a != nil { - variadicArgs[i] = a.(string) - } - } - run(args[0].(context.Context), args[1].(string), variadicArgs...) - }) - return _c -} - -func (_c *MockGit_Push_Call) Return(_a0 error) *MockGit_Push_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_Push_Call) RunAndReturn(run func(context.Context, string, ...string) error) *MockGit_Push_Call { - _c.Call.Return(run) - return _c -} - -// RemoveBranch provides a mock function with given fields: ctx, directory, branchName -func (_m *MockGit) RemoveBranch(ctx context.Context, directory string, branchName string) error { - ret := _m.Called(ctx, directory, branchName) - - if len(ret) == 0 { - panic("no return value specified for RemoveBranch") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { - r0 = rf(ctx, directory, branchName) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockGit_RemoveBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveBranch' -type MockGit_RemoveBranch_Call struct { - *mock.Call -} - -// RemoveBranch is a helper method to define mock.On call -// - ctx context.Context -// - directory string -// - branchName string -func (_e *MockGit_Expecter) RemoveBranch(ctx interface{}, directory interface{}, branchName interface{}) *MockGit_RemoveBranch_Call { - return &MockGit_RemoveBranch_Call{Call: _e.mock.On("RemoveBranch", ctx, directory, branchName)} -} - -func (_c *MockGit_RemoveBranch_Call) Run(run func(ctx context.Context, directory string, branchName string)) *MockGit_RemoveBranch_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string)) - }) - return _c -} - -func (_c *MockGit_RemoveBranch_Call) Return(_a0 error) *MockGit_RemoveBranch_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockGit_RemoveBranch_Call) RunAndReturn(run func(context.Context, string, string) error) *MockGit_RemoveBranch_Call { - _c.Call.Return(run) - return _c -} - -// NewMockGit creates a new instance of MockGit. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockGit(t interface { - mock.TestingT - Cleanup(func()) -}) *MockGit { - mock := &MockGit{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/git/v2/provider_test.go b/pkg/git/v2/provider_test.go deleted file mode 100644 index 5225614d..00000000 --- a/pkg/git/v2/provider_test.go +++ /dev/null @@ -1,520 +0,0 @@ -package v2 - -import ( - "context" - "encoding/base64" - "net/http" - "net/http/httptest" - "os" - "path" - "testing" - "time" - - gogit "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - ctrl "sigs.k8s.io/controller-runtime" - - "github.com/epam/edp-codebase-operator/v2/pkg/platform" -) - -func TestGitProvider_CheckPermissions(t *testing.T) { - user := "user" - pass := "pass" - - config := Config{ - Username: user, - Token: pass, - } - gp := NewGitProvider(config) - - bts, err := base64.StdEncoding.DecodeString(`MDAxZSMgc2VydmljZT1naXQtdXBsb2FkLXBhY2sKMDAwMDAxNTY2ZWNmMGVmMmMyZGZmYjc5NjAzM2U1YTAyMjE5YWY4NmVjNjU4NGU1IEhFQUQAbXVsdGlfYWNrIHRoaW4tcGFjayBzaWRlLWJhbmQgc2lkZS1iYW5kLTY0ayBvZnMtZGVsdGEgc2hhbGxvdyBkZWVwZW4tc2luY2UgZGVlcGVuLW5vdCBkZWVwZW4tcmVsYXRpdmUgbm8tcHJvZ3Jlc3MgaW5jbHVkZS10YWcgbXVsdGlfYWNrX2RldGFpbGVkIGFsbG93LXRpcC1zaGExLWluLXdhbnQgYWxsb3ctcmVhY2hhYmxlLXNoYTEtaW4td2FudCBuby1kb25lIHN5bXJlZj1IRUFEOnJlZnMvaGVhZHMvbWFzdGVyIGZpbHRlciBvYmplY3QtZm9ybWF0PXNoYTEgYWdlbnQ9Z2l0L2dpdGh1Yi1nNzhiNDUyNDEzZThiCjAwM2ZlOGQzZmZhYjU1Mjg5NWMxOWI5ZmNmN2FhMjY0ZDI3N2NkZTMzODgxIHJlZnMvaGVhZHMvYnJhbmNoCjAwM2Y2ZWNmMGVmMmMyZGZmYjc5NjAzM2U1YTAyMjE5YWY4NmVjNjU4NGU1IHJlZnMvaGVhZHMvbWFzdGVyCjAwM2ViOGU0NzFmNThiY2JjYTYzYjA3YmRhMjBlNDI4MTkwNDA5YzJkYjQ3IHJlZnMvcHVsbC8xL2hlYWQKMDAzZTk2MzJmMDI4MzNiMmY5NjEzYWZiNWU3NTY4MjEzMmIwYjIyZTRhMzEgcmVmcy9wdWxsLzIvaGVhZAowMDNmYzM3ZjU4YTEzMGNhNTU1ZTQyZmY5NmEwNzFjYjljY2IzZjQzNzUwNCByZWZzL3B1bGwvMi9tZXJnZQowMDAw`) - require.NoError(t, err) - - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - _, err = w.Write(bts) - assert.NoError(t, err, "failed to write response") - })) - defer s.Close() - - err = gp.CheckPermissions(context.Background(), s.URL) - require.NoError(t, err, "repo must be accessible") -} - -func TestGitProvider_CheckPermissions_NoRefs(t *testing.T) { - user := "user" - pass := "pass" - - config := Config{ - Username: user, - Token: pass, - } - gp := NewGitProvider(config) - - bts, err := base64.StdEncoding.DecodeString(`MDAxZSMgc2VydmljZT1naXQtdXBsb2FkLXBhY2sKMDAwMDAwZGUwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIGNhcGFiaWxpdGllc157fQAgaW5jbHVkZS10YWcgbXVsdGlfYWNrX2RldGFpbGVkIG11bHRpX2FjayBvZnMtZGVsdGEgc2lkZS1iYW5kIHNpZGUtYmFuZC02NGsgdGhpbi1wYWNrIG5vLXByb2dyZXNzIHNoYWxsb3cgbm8tZG9uZSBhZ2VudD1KR2l0L3Y1LjkuMC4yMDIwMDkwODA1MDEtci00MS1nNWQ5MjVlY2JiCjAwMDA=`) - require.NoError(t, err) - - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - _, err = w.Write(bts) - assert.NoError(t, err, "failed to write response") - })) - defer s.Close() - - mockLogger := platform.NewLoggerMock() - - // v2 implementation returns nil for empty repos (they are technically accessible, just empty) - // This is different from v1 which logged an error - err = gp.CheckPermissions(ctrl.LoggerInto(context.Background(), mockLogger), s.URL) - require.NoError(t, err, "v2 considers empty repos accessible") -} - -func TestGitProvider_CreateChildBranch(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - initRepo func(t *testing.T) string - parent string - child string - wantErr require.ErrorAssertionFunc - }{ - { - name: "should create child branch successfully", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - r, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - // Create initial commit on master branch - w, err := r.Worktree() - require.NoError(t, err) - - f, err := os.Create(path.Join(dir, "test.txt")) - require.NoError(t, err) - _, err = f.WriteString("test content") - require.NoError(t, err) - require.NoError(t, f.Close()) - - _, err = w.Add("test.txt") - require.NoError(t, err) - - _, err = w.Commit("initial commit", &gogit.CommitOptions{ - Author: &object.Signature{ - Name: "test", - Email: "test@example.com", - When: time.Now(), - }, - }) - require.NoError(t, err) - - // Create a parent branch and check it out so it exists as a proper reference - err = w.Checkout(&gogit.CheckoutOptions{ - Branch: plumbing.NewBranchReferenceName("parent-branch"), - Create: true, - }) - require.NoError(t, err) - - return dir - }, - parent: "parent-branch", - child: "child-branch", - wantErr: require.NoError, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - gp := NewGitProvider(Config{}) - dir := tt.initRepo(t) - - err := gp.CreateChildBranch(context.Background(), dir, tt.parent, tt.child) - tt.wantErr(t, err) - }) - } -} - -func TestGitProvider_RemoveBranch(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - initRepo func(t *testing.T) string - branch string - wantErr require.ErrorAssertionFunc - }{ - { - name: "should remove branch successfully", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - r, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - // Create initial commit - w, err := r.Worktree() - require.NoError(t, err) - - f, err := os.Create(path.Join(dir, "test.txt")) - require.NoError(t, err) - _, err = f.WriteString("test content") - require.NoError(t, err) - require.NoError(t, f.Close()) - - _, err = w.Add("test.txt") - require.NoError(t, err) - - _, err = w.Commit("initial commit", &gogit.CommitOptions{ - Author: &object.Signature{ - Name: "test", - Email: "test@example.com", - When: time.Now(), - }, - }) - require.NoError(t, err) - - // Create a new branch - err = w.Checkout(&gogit.CheckoutOptions{ - Branch: plumbing.NewBranchReferenceName("test-branch"), - Create: true, - }) - require.NoError(t, err) - - // Checkout back to master so we can delete test-branch - err = w.Checkout(&gogit.CheckoutOptions{ - Branch: plumbing.NewBranchReferenceName("master"), - }) - require.NoError(t, err) - - return dir - }, - branch: "test-branch", - wantErr: require.NoError, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - gp := NewGitProvider(Config{}) - dir := tt.initRepo(t) - - err := gp.RemoveBranch(context.Background(), dir, tt.branch) - tt.wantErr(t, err) - }) - } -} - -func TestGitProvider_CommitChanges(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - ops []CommitOps - initRepo func(t *testing.T) string - wantErr require.ErrorAssertionFunc - checkRepo func(t *testing.T, dir string) - }{ - { - name: "should commit changes successfully", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - _, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - _, err = os.Create(path.Join(dir, "config.yaml")) - require.NoError(t, err) - - return dir - }, - wantErr: require.NoError, - checkRepo: func(t *testing.T, dir string) { - r, err := gogit.PlainOpen(dir) - require.NoError(t, err) - - commits, err := r.CommitObjects() - require.NoError(t, err) - - count := 0 - _ = commits.ForEach(func(*object.Commit) error { - count++ - - return nil - }) - - require.Equalf(t, 1, count, "expected 1 commits, got %d", count) - }, - }, - { - name: "skip commit if no changes", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - _, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - return dir - }, - wantErr: require.NoError, - checkRepo: func(t *testing.T, dir string) { - r, err := gogit.PlainOpen(dir) - require.NoError(t, err) - - commits, err := r.CommitObjects() - require.NoError(t, err) - - count := 0 - _ = commits.ForEach(func(*object.Commit) error { - count++ - - return nil - }) - - require.Equalf(t, 0, count, "expected 0 commits, got %d", count) - }, - }, - { - name: "should create empty commit", - ops: []CommitOps{ - CommitAllowEmpty(), - }, - initRepo: func(t *testing.T) string { - dir := t.TempDir() - _, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - return dir - }, - wantErr: require.NoError, - checkRepo: func(t *testing.T, dir string) { - r, err := gogit.PlainOpen(dir) - require.NoError(t, err) - - commits, err := r.CommitObjects() - require.NoError(t, err) - - count := 0 - _ = commits.ForEach(func(*object.Commit) error { - count++ - - return nil - }) - - require.Equalf(t, 1, count, "expected 1 commits, got %d", count) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - gp := NewGitProvider(Config{}) - dir := tt.initRepo(t) - - err := gp.Commit(context.Background(), dir, "test commit message", tt.ops...) - tt.wantErr(t, err) - tt.checkRepo(t, dir) - }) - } -} - -func TestGitProvider_AddRemoteLink(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - remoteUrl string - initRepo func(t *testing.T) string - wantErr require.ErrorAssertionFunc - checkRepo func(t *testing.T, dir string) - }{ - { - name: "should add remote link successfully", - remoteUrl: "git@host:32/app.git", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - _, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - return dir - }, - wantErr: require.NoError, - checkRepo: func(t *testing.T, dir string) { - r, err := gogit.PlainOpen(dir) - require.NoError(t, err) - - remote, err := r.Remote("origin") - require.NoError(t, err) - - require.Equal(t, "origin", remote.Config().Name) - require.Len(t, remote.Config().URLs, 1) - require.Equal(t, "git@host:32/app.git", remote.Config().URLs[0]) - }, - }, - { - name: "empty git dir", - remoteUrl: "git@host:32/app.git", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - - return dir - }, - wantErr: require.Error, - checkRepo: func(t *testing.T, dir string) {}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - gp := NewGitProvider(Config{}) - dir := tt.initRepo(t) - - err := gp.AddRemoteLink(context.Background(), dir, tt.remoteUrl) - tt.wantErr(t, err) - tt.checkRepo(t, dir) - }) - } -} - -func TestGitProvider_CheckReference(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - initRepo func(t *testing.T) string - from string - wantErr require.ErrorAssertionFunc - }{ - { - name: "should return nil for empty reference", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - _, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - return dir - }, - from: "", - wantErr: require.NoError, - }, - { - name: "should find existing branch reference", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - r, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - // Create initial commit - w, err := r.Worktree() - require.NoError(t, err) - - f, err := os.Create(path.Join(dir, "test.txt")) - require.NoError(t, err) - _, err = f.WriteString("test content") - require.NoError(t, err) - require.NoError(t, f.Close()) - - _, err = w.Add("test.txt") - require.NoError(t, err) - - _, err = w.Commit("initial commit", &gogit.CommitOptions{ - Author: &object.Signature{ - Name: "test", - Email: "test@example.com", - When: time.Now(), - }, - }) - require.NoError(t, err) - - // Create and checkout a new branch - err = w.Checkout(&gogit.CheckoutOptions{ - Branch: plumbing.NewBranchReferenceName("test-branch"), - Create: true, - }) - require.NoError(t, err) - - return dir - }, - from: "test-branch", - wantErr: require.NoError, - }, - { - name: "should find existing commit reference", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - r, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - - // Create initial commit - w, err := r.Worktree() - require.NoError(t, err) - - f, err := os.Create(path.Join(dir, "test.txt")) - require.NoError(t, err) - _, err = f.WriteString("test content") - require.NoError(t, err) - require.NoError(t, f.Close()) - - _, err = w.Add("test.txt") - require.NoError(t, err) - - commit, err := w.Commit("initial commit", &gogit.CommitOptions{ - Author: &object.Signature{ - Name: "test", - Email: "test@example.com", - When: time.Now(), - }, - }) - require.NoError(t, err) - - // Store the commit hash for the test - t.Logf("Created commit with hash: %s", commit.String()) - - return dir - }, - from: "", // Will be set dynamically - wantErr: require.NoError, - }, - { - name: "should return error for non-existent reference", - initRepo: func(t *testing.T) string { - dir := t.TempDir() - _, err := gogit.PlainInit(dir, false) - require.NoError(t, err) - return dir - }, - from: "non-existent", - wantErr: require.Error, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - gp := NewGitProvider(Config{}) - dir := tt.initRepo(t) - - // For the commit reference test, we need to get the actual commit hash - if tt.name == "should find existing commit reference" { - r, err := gogit.PlainOpen(dir) - require.NoError(t, err) - - ref, err := r.Head() - require.NoError(t, err) - - tt.from = ref.Hash().String() - t.Logf("Using commit hash: %s", tt.from) - } - - err := gp.CheckReference(context.Background(), dir, tt.from) - tt.wantErr(t, err) - }) - } -} diff --git a/sonar-project.properties b/sonar-project.properties index 062157f4..84c25733 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,4 +3,4 @@ sonar.projectName=codebase-operator sonar.projectVersion=1.0 sonar.go.coverage.reportPaths=coverage.out sonar.test.inclusions=**/*_test.go -sonar.exclusions=**/build/**,**/config/**,**/deploy-templates/**,**/factory.go,**/mock_*.go,**/*_mock.go,**/.github/**,**/api/**,**/hack/**,**/main.go,**/*generated.go,Dockerfile,**/git/v2/provider.go,**/chain/put_project.go +sonar.exclusions=**/build/**,**/config/**,**/deploy-templates/**,**/factory.go,**/mock_*.go,**/*_mock.go,**/.github/**,**/api/**,**/hack/**,**/main.go,**/*generated.go,Dockerfile,**/git/provider.go