diff --git a/artifactory_test.go b/artifactory_test.go index 9ba575230..1e0468518 100644 --- a/artifactory_test.go +++ b/artifactory_test.go @@ -6885,3 +6885,80 @@ func validateSearchPatternWithAPI(t *testing.T, pattern string, recursive bool) readerGetErrorAndAssert(t, readerNoDate) readerCloseAndAssert(t, readerNoDate) } + +// TestUploadMultipleFilesWithCIVcsProps tests that CI VCS properties are set on multiple uploaded files. +// This test: +// - Creates 15 files dynamically during the test +// - Uploads all files using jf rt u +// - Verifies vcs.provider, vcs.org, vcs.repo properties are set on ALL 15 files +// - Fails if any property is missing on any file +func TestUploadMultipleFilesWithCIVcsProps(t *testing.T) { + initArtifactoryTest(t, "") + const numFiles = 15 + + // Setup GitHub Actions environment (uses real env vars on CI, mock values locally) + cleanupEnv, actualOrg, actualRepo := tests.SetupGitHubActionsEnv(t) + defer cleanupEnv() + + // Create temp directory for test files + tmpDir, createTempDirCallback := coretests.CreateTempDirWithCallbackAndAssert(t) + defer createTempDirCallback() + + // Create 15 files dynamically + var createdFiles []string + for i := 1; i <= numFiles; i++ { + fileName := fmt.Sprintf("civcs-test-file-%d.txt", i) + filePath := filepath.Join(tmpDir, fileName) + content := fmt.Sprintf("Test file %d for CI VCS properties verification. Timestamp: %d", i, time.Now().UnixNano()) + err := os.WriteFile(filePath, []byte(content), 0644) + require.NoError(t, err, "Failed to create test file: %s", fileName) + createdFiles = append(createdFiles, fileName) + } + + // Upload all files to Artifactory + uploadPattern := filepath.Join(tmpDir, "civcs-test-file-*.txt") + targetPath := tests.RtRepo1 + "/civcs-multi-upload/" + runRt(t, "upload", uploadPattern, targetPath, "--flat") + + // Create service manager for getting artifact properties + serviceManager, err := utils.CreateServiceManager(serverDetails, 3, 1000, false) + require.NoError(t, err, "Failed to create service manager") + + // Verify CI VCS properties on each uploaded file + validatedCount := 0 + for _, fileName := range createdFiles { + fullPath := tests.RtRepo1 + "/civcs-multi-upload/" + fileName + + props, err := serviceManager.GetItemProps(fullPath) + require.NoError(t, err, "Failed to get properties for file: %s", fullPath) + require.NotNil(t, props, "Properties are nil for file: %s", fullPath) + + // Validate vcs.provider property + require.Contains(t, props.Properties, "vcs.provider", + "Missing vcs.provider property on file: %s", fileName) + require.Contains(t, props.Properties["vcs.provider"], "github", + "Wrong vcs.provider value on file: %s. Expected 'github', got: %v", fileName, props.Properties["vcs.provider"]) + + // Validate vcs.org property + require.Contains(t, props.Properties, "vcs.org", + "Missing vcs.org property on file: %s", fileName) + require.Contains(t, props.Properties["vcs.org"], actualOrg, + "Wrong vcs.org value on file: %s. Expected '%s', got: %v", fileName, actualOrg, props.Properties["vcs.org"]) + + // Validate vcs.repo property + require.Contains(t, props.Properties, "vcs.repo", + "Missing vcs.repo property on file: %s", fileName) + require.Contains(t, props.Properties["vcs.repo"], actualRepo, + "Wrong vcs.repo value on file: %s. Expected '%s', got: %v", fileName, actualRepo, props.Properties["vcs.repo"]) + + validatedCount++ + } + + // Ensure we validated exactly the expected number of files + require.Equal(t, numFiles, validatedCount, + "Expected to validate %d files, but validated %d", numFiles, validatedCount) + + t.Logf("Successfully validated CI VCS properties on all %d files", validatedCount) + + cleanArtifactoryTest() +} diff --git a/conan_test.go b/conan_test.go index 324c462cd..fc8bee20a 100644 --- a/conan_test.go +++ b/conan_test.go @@ -9,6 +9,7 @@ import ( buildinfo "github.com/jfrog/build-info-go/entities" biutils "github.com/jfrog/build-info-go/utils" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" coretests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" "github.com/jfrog/jfrog-cli/inttestutils" "github.com/jfrog/jfrog-cli/utils/tests" @@ -467,3 +468,104 @@ func cleanupConanRemote() { _ = exec.Command("conan", "remote", "remove", tests.ConanVirtualRepo).Run() _ = exec.Command("conan", "remote", "remove", tests.ConanLocalRepo).Run() } + +// TestConanBuildPublishWithCIVcsProps tests that CI VCS properties are set on Conan artifacts +// when running build-publish in a CI environment (GitHub Actions). +// Conan packages are published via Conan client; build-publish retrieves artifact paths +// from Build Info and applies properties via batch API. +func TestConanBuildPublishWithCIVcsProps(t *testing.T) { + initConanTest(t) + + buildName := tests.ConanBuildName + "-civcs" + buildNumber := "1" + + // Setup GitHub Actions environment (uses real env vars on CI, mock values locally) + cleanupEnv, actualOrg, actualRepo := tests.SetupGitHubActionsEnv(t) + defer cleanupEnv() + + // Clean old build + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + // Prepare project + projectPath := createConanProject(t, "conan-civcs-test") + wd, err := os.Getwd() + require.NoError(t, err) + chdirCallback := clientTestUtils.ChangeDirWithCallback(t, wd, projectPath) + defer chdirCallback() + + // Configure Conan remote + configureConanRemote(t) + defer cleanupConanRemote() + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + + // Run conan create + createArgs := []string{ + "conan", "create", ".", + "--build=missing", + "--build-name=" + buildName, + "--build-number=" + buildNumber, + } + assert.NoError(t, jfrogCli.Exec(createArgs...)) + + // Run conan upload + uploadArgs := []string{ + "conan", "upload", "cli-test-package/*", + "-r", tests.ConanLocalRepo, + "--confirm", + "--build-name=" + buildName, + "--build-number=" + buildNumber, + } + assert.NoError(t, jfrogCli.Exec(uploadArgs...)) + + // Publish build info - should set CI VCS props on artifacts + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + // Get the published build info to find artifact paths + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + require.NoError(t, err) + require.True(t, found, "Build info was not found") + + // Create service manager for getting artifact properties + serviceManager, err := utils.CreateServiceManager(serverDetails, 3, 1000, false) + require.NoError(t, err) + + // Verify VCS properties on each artifact from build info + // Note: Conan artifacts may not have OriginalDeploymentRepo set, so we use Path directly as fallback + artifactCount := 0 + for _, module := range publishedBuildInfo.BuildInfo.Modules { + for _, artifact := range module.Artifacts { + // Use same fallback logic as CI VCS: OriginalDeploymentRepo + Path, or Path directly + var fullPath string + switch { + case artifact.OriginalDeploymentRepo != "": + fullPath = artifact.OriginalDeploymentRepo + "/" + artifact.Path + case artifact.Path != "": + fullPath = artifact.Path + default: + continue // Skip artifacts without any path info + } + + props, err := serviceManager.GetItemProps(fullPath) + assert.NoError(t, err, "Failed to get properties for artifact: %s", fullPath) + if props == nil { + continue + } + + // Validate VCS properties + assert.Contains(t, props.Properties, "vcs.provider", "Missing vcs.provider on %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.provider"], "github", "Wrong vcs.provider on %s", artifact.Name) + + assert.Contains(t, props.Properties, "vcs.org", "Missing vcs.org on %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.org"], actualOrg, "Wrong vcs.org on %s", artifact.Name) + + assert.Contains(t, props.Properties, "vcs.repo", "Missing vcs.repo on %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.repo"], actualRepo, "Wrong vcs.repo on %s", artifact.Name) + + artifactCount++ + } + } + + assert.Greater(t, artifactCount, 0, "No artifacts were validated for CI VCS properties") +} diff --git a/go_test.go b/go_test.go index a9e593cc7..1c9a2e304 100644 --- a/go_test.go +++ b/go_test.go @@ -2,8 +2,6 @@ package main import ( "fmt" - "github.com/jfrog/jfrog-client-go/http/httpclient" - "github.com/stretchr/testify/require" "net/http" "os" "os/exec" @@ -12,20 +10,21 @@ import ( biutils "github.com/jfrog/build-info-go/utils" - coretests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" - clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" - buildinfo "github.com/jfrog/build-info-go/entities" "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/golang" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/common/build" "github.com/jfrog/jfrog-cli-core/v2/common/commands" "github.com/jfrog/jfrog-cli-core/v2/common/spec" + coretests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" "github.com/jfrog/jfrog-cli/inttestutils" - "github.com/jfrog/jfrog-cli/utils/tests" + "github.com/jfrog/jfrog-client-go/http/httpclient" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/log" + clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestGoConfigWithModuleNameChange(t *testing.T) { @@ -420,3 +419,87 @@ func TestSetupGoCommand(t *testing.T) { assert.Equal(t, http.StatusOK, res.StatusCode) } } + +// TestGoBuildPublishWithCIVcsProps tests that CI VCS properties are set on Go artifacts +// when running go-publish followed by build-publish in a CI environment (GitHub Actions). +// Go supports direct upload via 'jf gp' which sets CI VCS properties during upload. +func TestGoBuildPublishWithCIVcsProps(t *testing.T) { + _, cleanUpFunc := initGoTest(t) + defer cleanUpFunc() + + buildName := tests.GoBuildName + "-civcs" + buildNumber := "1" + + // Setup GitHub Actions environment (uses real env vars on CI, mock values locally) + cleanupEnv, actualOrg, actualRepo := tests.SetupGitHubActionsEnv(t) + defer cleanupEnv() + + // Clean old build + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + wd, err := os.Getwd() + assert.NoError(t, err, "Failed to get current dir") + + // Prepare and build the Go project + prepareGoProject("project1", t, true) + defer clientTestUtils.ChangeDirAndAssert(t, wd) + + // Build the Go project with build info collection + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err = execGo(jfrogCli, "go", "build", "--mod=mod", "--build-name="+buildName, "--build-number="+buildNumber) + assert.NoError(t, err) + + // Publish the Go module to Artifactory with build info collection + // This is where CI VCS properties should be set during direct upload + err = execGo(jfrogCli, "gp", "--build-name="+buildName, "--build-number="+buildNumber, "v1.0.0") + assert.NoError(t, err) + + // Publish build info - should also set CI VCS props via batch update if not already set + err = execGo(artifactoryCli, "bp", buildName, buildNumber) + assert.NoError(t, err) + + // Get the published build info to find artifact paths + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found, "Build info was not found") + + // Create service manager for getting artifact properties + serviceManager, err := utils.CreateServiceManager(serverDetails, 3, 1000, false) + assert.NoError(t, err) + + // Verify VCS properties on each artifact from build info + // Use same fallback logic as CI VCS: OriginalDeploymentRepo + Path, or Path directly + artifactCount := 0 + for _, module := range publishedBuildInfo.BuildInfo.Modules { + for _, artifact := range module.Artifacts { + var fullPath string + switch { + case artifact.OriginalDeploymentRepo != "": + fullPath = artifact.OriginalDeploymentRepo + "/" + artifact.Path + case artifact.Path != "": + fullPath = artifact.Path + default: + continue // Skip artifacts without any path info + } + + props, err := serviceManager.GetItemProps(fullPath) + assert.NoError(t, err, "Failed to get properties for artifact: %s", fullPath) + assert.NotNil(t, props, "Properties are nil for artifact: %s", fullPath) + + // Validate VCS properties + assert.Contains(t, props.Properties, "vcs.provider", "Missing vcs.provider on %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.provider"], "github", "Wrong vcs.provider on %s", artifact.Name) + + assert.Contains(t, props.Properties, "vcs.org", "Missing vcs.org on %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.org"], actualOrg, "Wrong vcs.org on %s", artifact.Name) + + assert.Contains(t, props.Properties, "vcs.repo", "Missing vcs.repo on %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.repo"], actualRepo, "Wrong vcs.repo on %s", artifact.Name) + + artifactCount++ + } + } + + assert.Greater(t, artifactCount, 0, "No artifacts were validated for CI VCS properties") +} diff --git a/helm_test.go b/helm_test.go index f2a2c549e..279e4f069 100644 --- a/helm_test.go +++ b/helm_test.go @@ -16,9 +16,11 @@ import ( "time" buildinfo "github.com/jfrog/build-info-go/entities" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" + "github.com/jfrog/jfrog-cli/inttestutils" "github.com/jfrog/jfrog-cli/utils/tests" clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" "github.com/stretchr/testify/assert" @@ -803,6 +805,159 @@ func TestHelmPackageAndPushWithBuildInfo(t *testing.T) { validateHelmBuildInfo(t, publishedBuildInfo.BuildInfo, buildName, true, true) } +// TestHelmBuildPublishWithCIVcsProps tests that CI VCS properties are set on Helm artifacts +// when running build-publish in a CI environment (GitHub Actions). +// Helm charts are published via Helm client; build-publish retrieves artifact paths +// from Build Info and applies CI VCS properties via optimized batch SetProps API call. +func TestHelmBuildPublishWithCIVcsProps(t *testing.T) { + initHelmTest(t) + defer cleanHelmTest(t) + + buildName := tests.HelmBuildName + "-civcs" + buildNumber := "1" + + // Setup GitHub Actions environment (uses real env vars on CI, mock values locally) + cleanupEnv, actualOrg, actualRepo := tests.SetupGitHubActionsEnv(t) + defer cleanupEnv() + + // Clean old build + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + chartDir := createTestHelmChartWithDependencies(t, "test-chart-civcs", "0.2.0") + defer func() { + if err := os.RemoveAll(chartDir); err != nil { + t.Logf("Warning: Failed to remove test chart directory %s: %v", chartDir, err) + } + }() + + originalDir, err := os.Getwd() + require.NoError(t, err) + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Warning: Failed to change back to original directory: %v", err) + } + }() + + err = os.Chdir(chartDir) + require.NoError(t, err) + + // Run helm dependency update + helmCmd := exec.Command("helm", "dependency", "update") + helmCmd.Dir = chartDir + err = helmCmd.Run() + require.NoError(t, err, "helm dependency update should succeed") + + // Run helm package with build info (collects dependencies) + jfrogCli := coreTests.NewJfrogCli(execMain, "jfrog", "") + args := []string{ + "helm", "package", ".", + "--build-name=" + buildName, + "--build-number=" + buildNumber, + } + err = jfrogCli.Exec(args...) + require.NoError(t, err, "helm package should succeed") + + // Get the packaged chart file + chartFiles, err := filepath.Glob(filepath.Join(chartDir, "*.tgz")) + require.NoError(t, err) + require.Greater(t, len(chartFiles), 0, "Chart package file should be created") + chartFile := filepath.Base(chartFiles[0]) + + // Setup registry for push + parsedURL, err := url.Parse(serverDetails.ArtifactoryUrl) + require.NoError(t, err) + registryHost := parsedURL.Host + registryURL := fmt.Sprintf("oci://%s/%s", registryHost, tests.HelmLocalRepo) + + if !isRepoExist(tests.HelmLocalRepo) { + t.Skipf("Repository %s does not exist. Skipping test.", tests.HelmLocalRepo) + } + + err = loginHelmRegistry(t, registryHost) + if err != nil { + errorMsg := strings.ToLower(err.Error()) + if strings.Contains(errorMsg, "account temporarily locked") { + t.Skip("Artifactory account is temporarily locked. Skipping test.") + } + if strings.Contains(errorMsg, "http response to https") || + strings.Contains(errorMsg, "tls: first record does not look like a tls handshake") { + t.Skip("Helm registry login failed due to HTTPS/HTTP mismatch. Skipping test.") + } + } + require.NoError(t, err, "helm registry login should succeed") + + // Run helm push with the same build name/number (adds artifacts to existing build info) + args = []string{ + "helm", "push", chartFile, + registryURL, + "--build-name=" + buildName, + "--build-number=" + buildNumber, + } + err = jfrogCli.Exec(args...) + if err != nil { + errorMsg := strings.ToLower(err.Error()) + if strings.Contains(errorMsg, "404") || + strings.Contains(errorMsg, "not found") || + strings.Contains(errorMsg, "exit status 1") { + t.Skip("OCI registry API not accessible (404). Skipping test.") + } + } + require.NoError(t, err, "helm push should succeed") + + // Publish build info - should set CI VCS props on artifacts + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + // Get the published build info + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + require.NoError(t, err, "Failed to get build info") + require.True(t, found, "build info should be found") + + // Verify build info has artifacts + require.Greater(t, len(publishedBuildInfo.BuildInfo.Modules), 0, "Build info should have modules") + + // Create service manager for getting artifact properties + serviceManager, err := utils.CreateServiceManager(serverDetails, 3, 1000, false) + require.NoError(t, err) + + // Verify VCS properties on each artifact from build info + // Use same fallback logic as CI VCS: OriginalDeploymentRepo + Path, or Path directly + artifactCount := 0 + for _, module := range publishedBuildInfo.BuildInfo.Modules { + for _, artifact := range module.Artifacts { + var fullPath string + switch { + case artifact.OriginalDeploymentRepo != "": + fullPath = artifact.OriginalDeploymentRepo + "/" + artifact.Path + case artifact.Path != "": + fullPath = artifact.Path + default: + continue // Skip artifacts without any path info + } + + props, err := serviceManager.GetItemProps(fullPath) + assert.NoError(t, err, "Failed to get properties for artifact: %s", fullPath) + if props == nil { + continue + } + + // Validate VCS properties + assert.Contains(t, props.Properties, "vcs.provider", "Missing vcs.provider on %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.provider"], "github", "Wrong vcs.provider on %s", artifact.Name) + + assert.Contains(t, props.Properties, "vcs.org", "Missing vcs.org on %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.org"], actualOrg, "Wrong vcs.org on %s", artifact.Name) + + assert.Contains(t, props.Properties, "vcs.repo", "Missing vcs.repo on %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.repo"], actualRepo, "Wrong vcs.repo on %s", artifact.Name) + + artifactCount++ + } + } + + assert.Greater(t, artifactCount, 0, "No artifacts were validated for CI VCS properties") +} + // InitHelmTests initializes Helm tests func InitHelmTests() { initArtifactoryCli() diff --git a/nuget_test.go b/nuget_test.go index b54965706..bc500c0e8 100644 --- a/nuget_test.go +++ b/nuget_test.go @@ -475,3 +475,62 @@ func TestDotnetRequestedByDeterminism(t *testing.T) { t.Logf("Successfully verified RequestedBy determinism across %d runs", numRuns) cleanTestsHomeEnv() } + +// TestNugetBuildPublishWithCIVcsProps tests that CI VCS properties integration works +// with NuGet in a CI environment (GitHub Actions). +// Note: NuGet restore only has dependencies (not artifacts), so this test validates +// that build-info collection works correctly in CI environment. CI VCS properties +// are set on artifacts via build-publish when packages are pushed (nuget push). +func TestNugetBuildPublishWithCIVcsProps(t *testing.T) { + initNugetTest(t) + defer cleanTestsHomeEnv() + + buildName := tests.NuGetBuildName + "-civcs" + buildNumber := "1" + + // Setup GitHub Actions environment (uses real env vars on CI, mock values locally) + cleanupEnv, actualOrg, actualRepo := tests.SetupGitHubActionsEnv(t) + defer cleanupEnv() + + // Clean old build + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + // Create NuGet project and config + projectPath := createNugetProject(t, "reference") + err := createConfigFileForTest([]string{projectPath}, tests.NugetRemoteRepo, "", t, project.Nuget, false) + require.NoError(t, err) + + wd, err := os.Getwd() + require.NoError(t, err) + chdirCallback := clientTestUtils.ChangeDirWithCallback(t, wd, projectPath) + defer chdirCallback() + + // Run NuGet restore with build info collection + args := []string{dotnetUtils.Nuget.String(), "restore"} + allowInsecureConnectionForTests(&args) + args = append(args, "--build-name="+buildName, "--build-number="+buildNumber) + + err = runNuGet(t, args...) + require.NoError(t, err) + + // Publish build info + require.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + // Get the published build info + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + require.NoError(t, err) + require.True(t, found, "Build info was not found") + + // Validate build info was created correctly + require.NotEmpty(t, publishedBuildInfo.BuildInfo.Modules, "Build info should have modules") + assert.Greater(t, len(publishedBuildInfo.BuildInfo.Modules[0].Dependencies), 0, "Build info should have dependencies") + assert.Equal(t, buildName, publishedBuildInfo.BuildInfo.Name, "Build name should match") + assert.Equal(t, buildNumber, publishedBuildInfo.BuildInfo.Number, "Build number should match") + + // Verify CI environment was detected (for nuget push scenarios, CI VCS props would be set) + assert.NotEmpty(t, actualOrg, "CI org should be detected") + assert.NotEmpty(t, actualRepo, "CI repo should be detected") + t.Logf("NuGet build info created with %d dependencies in CI environment (org=%s, repo=%s)", + len(publishedBuildInfo.BuildInfo.Modules[0].Dependencies), actualOrg, actualRepo) +} diff --git a/pip_test.go b/pip_test.go index 5ba4af1ba..4ee8ac9d6 100644 --- a/pip_test.go +++ b/pip_test.go @@ -688,3 +688,92 @@ func TestSetupPipCommand(t *testing.T) { assert.Equal(t, http.StatusOK, res.StatusCode) } } + +// TestTwineBuildPublishWithCIVcsProps tests that CI VCS properties are set on Twine artifacts +// when running build-publish in a CI environment (GitHub Actions). +// Twine relies on build-publish to set CI VCS properties via batch AQL query. +func TestTwineBuildPublishWithCIVcsProps(t *testing.T) { + initPipTest(t) + + buildName := tests.PipBuildName + "-civcs" + buildNumber := "1" + + // Setup GitHub Actions environment (uses real env vars on CI, mock values locally) + cleanupEnv, actualOrg, actualRepo := tests.SetupGitHubActionsEnv(t) + defer cleanupEnv() + + // Populate cli config with 'default' server. + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + cleanVirtualEnv, err := prepareVirtualEnv(t) + assert.NoError(t, err) + defer cleanVirtualEnv() + + // Clean old build + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + projectPath := createPypiProject(t, "twine-civcs", "pyproject", "twine") + + wd, err := os.Getwd() + assert.NoError(t, err, "Failed to get current dir") + chdirCallback := clientTestUtils.ChangeDirWithCallback(t, wd, projectPath) + defer chdirCallback() + + // Run twine upload with build info collection + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + args := []string{"twine", "upload", "dist/*", "--build-name=" + buildName, "--build-number=" + buildNumber} + err = jfrogCli.Exec(args...) + assert.NoError(t, err, "Failed executing twine upload command") + + // Publish build info - should set CI VCS props on artifacts + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + // Get the published build info to find artifact paths + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found, "Build info was not found") + + // Create service manager for getting artifact properties + serviceManager, err := utils.CreateServiceManager(serverDetails, 3, 1000, false) + assert.NoError(t, err) + + // Verify VCS properties on each artifact from build info + // Use same fallback logic as CI VCS: OriginalDeploymentRepo + Path, or Path directly + artifactCount := 0 + for _, module := range publishedBuildInfo.BuildInfo.Modules { + for _, artifact := range module.Artifacts { + var fullPath string + switch { + case artifact.OriginalDeploymentRepo != "": + fullPath = artifact.OriginalDeploymentRepo + "/" + artifact.Path + case artifact.Path != "": + fullPath = artifact.Path + default: + continue // Skip artifacts without any path info + } + + props, err := serviceManager.GetItemProps(fullPath) + assert.NoError(t, err, "Failed to get properties for artifact: %s", fullPath) + assert.NotNil(t, props, "Properties are nil for artifact: %s", fullPath) + + // Validate VCS properties + assert.Contains(t, props.Properties, "vcs.provider", "Missing vcs.provider on %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.provider"], "github", "Wrong vcs.provider on %s", artifact.Name) + + assert.Contains(t, props.Properties, "vcs.org", "Missing vcs.org on %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.org"], actualOrg, "Wrong vcs.org on %s", artifact.Name) + + assert.Contains(t, props.Properties, "vcs.repo", "Missing vcs.repo on %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.repo"], actualRepo, "Wrong vcs.repo on %s", artifact.Name) + + artifactCount++ + } + } + + assert.Greater(t, artifactCount, 0, "No artifacts were validated for CI VCS properties") +} diff --git a/poetry_test.go b/poetry_test.go index 2c871c660..0c6c54e37 100644 --- a/poetry_test.go +++ b/poetry_test.go @@ -12,6 +12,7 @@ import ( buildinfo "github.com/jfrog/build-info-go/entities" biutils "github.com/jfrog/build-info-go/utils" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" coretests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" "github.com/jfrog/jfrog-client-go/http/httpclient" @@ -629,3 +630,98 @@ func TestPoetryFlexPackFeatures(t *testing.T) { inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, "poetry-checksum-test", artHttpDetails) }) } + +// TestPoetryBuildPublishWithCIVcsProps tests that CI VCS properties are set on Poetry artifacts +// when running build-publish in a CI environment (GitHub Actions). +// Poetry relies on build-publish to set CI VCS properties via batch AQL query. +func TestPoetryBuildPublishWithCIVcsProps(t *testing.T) { + initPoetryTest(t) + + buildName := tests.PoetryBuildName + "-civcs" + buildNumber := "1" + + // Setup GitHub Actions environment (uses real env vars on CI, mock values locally) + cleanupEnv, actualOrg, actualRepo := tests.SetupGitHubActionsEnv(t) + defer cleanupEnv() + + // Populate cli config with 'default' server + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + // Clean old build + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + projectPath := createPoetryProject(t, "poetry-civcs", "poetryproject") + + wd, err := os.Getwd() + assert.NoError(t, err) + chdirCallback := clientTestUtils.ChangeDirWithCallback(t, wd, projectPath) + defer chdirCallback() + + // Build the package first + buildCmd := exec.Command("poetry", "build") + buildCmd.Dir = projectPath + assert.NoError(t, buildCmd.Run(), "Failed to build Poetry package") + + // Run poetry publish with build info collection + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + args := []string{ + "poetry", "publish", + "--build-name=" + buildName, + "--build-number=" + buildNumber, + "--repository=" + tests.PoetryLocalRepo, + } + err = jfrogCli.Exec(args...) + assert.NoError(t, err, "Failed executing poetry publish command") + + // Publish build info - should set CI VCS props on artifacts + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + // Get the published build info to find artifact paths + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found, "Build info was not found") + + // Create service manager for getting artifact properties + serviceManager, err := utils.CreateServiceManager(serverDetails, 3, 1000, false) + assert.NoError(t, err) + + // Verify VCS properties on each artifact from build info + // Use same fallback logic as CI VCS: OriginalDeploymentRepo + Path, or Path directly + artifactCount := 0 + for _, module := range publishedBuildInfo.BuildInfo.Modules { + for _, artifact := range module.Artifacts { + var fullPath string + switch { + case artifact.OriginalDeploymentRepo != "": + fullPath = artifact.OriginalDeploymentRepo + "/" + artifact.Path + case artifact.Path != "": + fullPath = artifact.Path + default: + continue // Skip artifacts without any path info + } + + props, err := serviceManager.GetItemProps(fullPath) + assert.NoError(t, err, "Failed to get properties for artifact: %s", fullPath) + assert.NotNil(t, props, "Properties are nil for artifact: %s", fullPath) + + // Validate VCS properties + assert.Contains(t, props.Properties, "vcs.provider", "Missing vcs.provider on %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.provider"], "github", "Wrong vcs.provider on %s", artifact.Name) + + assert.Contains(t, props.Properties, "vcs.org", "Missing vcs.org on %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.org"], actualOrg, "Wrong vcs.org on %s", artifact.Name) + + assert.Contains(t, props.Properties, "vcs.repo", "Missing vcs.repo on %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.repo"], actualRepo, "Wrong vcs.repo on %s", artifact.Name) + + artifactCount++ + } + } + + assert.Greater(t, artifactCount, 0, "No artifacts were validated for CI VCS properties") +}