Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
8ef998e
chore: bump jfrog-cli-artifactory for local git VCS fallback
attiasas Jun 14, 2026
edabc01
test: add local git VCS e2e helpers and fixtures
attiasas Jun 14, 2026
25ba219
test: e2e local git VCS props on rt upload flows
attiasas Jun 14, 2026
1e1ef7e
test: e2e local git VCS props on go and gradle publish
attiasas Jun 14, 2026
7d5ab43
test: e2e local git VCS props on build-publish
attiasas Jun 14, 2026
91b95ec
format, fix static
attiasas Jun 14, 2026
b18e7a9
fix tests
attiasas Jun 14, 2026
2242fc6
update deps
attiasas Jun 14, 2026
dfe1021
update dep
attiasas Jun 15, 2026
0946bdc
fix tests
attiasas Jun 15, 2026
5852836
fix tests for gradle fix
attiasas Jun 15, 2026
722af11
update dep to expend_vsc_detection
attiasas Jun 15, 2026
8cd321d
fix tests in CI
attiasas Jun 16, 2026
b0a82a6
test: add shared local-git VCS validation for build-info artifacts
attiasas Jun 16, 2026
9b97e22
test: e2e local git VCS props on conan upload
attiasas Jun 16, 2026
16767d2
test: e2e local git VCS props on maven build-publish
attiasas Jun 16, 2026
f7dc24f
test: e2e local git VCS props on Gradle FlexPack publish
attiasas Jun 16, 2026
447593b
test: e2e local git VCS props on npm publish
attiasas Jun 16, 2026
093068e
test: e2e local git VCS props on pnpm publish
attiasas Jun 16, 2026
d956944
test: e2e local git VCS props on twine publish
attiasas Jun 16, 2026
eee0ded
test: e2e local git VCS props on uv publish
attiasas Jun 16, 2026
c3b15bc
test: e2e local git VCS props on docker push
attiasas Jun 16, 2026
d582695
test: e2e local git VCS props on helm push
attiasas Jun 16, 2026
5ba23a3
test: e2e local git VCS props on nix copy
attiasas Jun 16, 2026
295cefd
test: e2e local git VCS props on huggingface upload
attiasas Jun 16, 2026
91ca785
test: e2e local git VCS props on terraform publish
attiasas Jun 16, 2026
2da7cdd
fix(tests): correct maven repo constant and UV artifact path resolution
attiasas Jun 16, 2026
16b05d3
Merge remote-tracking branch 'upstream/master' into e2e_tests_rt_vcs
attiasas Jun 18, 2026
633f019
update deps
attiasas Jun 18, 2026
e08d2ba
Merge remote-tracking branch 'upstream/master' into e2e_tests_rt_vcs
attiasas Jun 18, 2026
ccd4c5b
move container tests to diff PR #3554
attiasas Jun 18, 2026
22a12aa
format
attiasas Jun 18, 2026
8bedf6c
Merge remote-tracking branch 'upstream/master' into e2e_tests_rt_vcs
attiasas Jun 18, 2026
54b992a
move tests to #3556
attiasas Jun 18, 2026
8f9e31f
move more tests to #3556
attiasas Jun 18, 2026
7cec47f
Merge remote-tracking branch 'upstream/master' into e2e_tests_rt_vcs
attiasas Jun 23, 2026
c55bef1
update deps
attiasas Jun 23, 2026
08a4964
Merge remote-tracking branch 'upstream/master' into e2e_tests_rt_vcs
attiasas Jun 23, 2026
db444ad
update dep
attiasas Jun 25, 2026
b50c6c6
Merge remote-tracking branch 'upstream/master' into e2e_tests_rt_vcs
attiasas Jun 28, 2026
30e9729
Merge remote-tracking branch 'upstream/master' into e2e_tests_rt_vcs
attiasas Jul 1, 2026
fb417ac
update dep after artifactory merge
attiasas Jul 1, 2026
e128c8f
Merge remote-tracking branch 'upstream/master' into e2e_tests_rt_vcs
attiasas Jul 1, 2026
e91f26f
update go sum
attiasas Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions artifactory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7181,3 +7181,144 @@ func TestUploadMultipleFilesWithCIVcsProps(t *testing.T) {

cleanArtifactoryTest()
}

// TestUploadWithLocalGitVcsProps verifies civcs local git fallback on rt upload
// when CI VCS env vars are absent but VCS collection is enabled.
func TestUploadWithLocalGitVcsProps(t *testing.T) {
initArtifactoryTest(t, "")

cleanupEnv := tests.SetupLocalGitVcsEnv(t)
defer cleanupEnv()

testDir := tests.CopyVcsGitFixture(t, tests.Temp)
targetPath := tests.RtRepo1 + "/local-git-vcs/"

runRt(t, "upload", filepath.Join(testDir, "*.in"), targetPath, "--flat=true")

resultItems := searchItemsInArtifactory(t, tests.SearchRepo1ByInSuffix)
assert.NotZero(t, len(resultItems))

var uploaded []rtutils.ResultItem
for _, item := range resultItems {
if item.Name == "a1.in" || item.Name == "a2.in" {
uploaded = append(uploaded, item)
}
}
assert.Len(t, uploaded, 2)

tests.ValidateLocalGitVcsPropsOnArtifacts(t, uploaded,
tests.VcsFixtureMainURL, tests.VcsFixtureMainRevision, tests.VcsFixtureMainBranch)

cleanArtifactoryTest()
}

// TestUploadWithLocalGitVcsPropsNestedRepo verifies upload from a subdirectory
// resolves the nearest .git (OtherGit), not the parent repo.
func TestUploadWithLocalGitVcsPropsNestedRepo(t *testing.T) {
initArtifactoryTest(t, "")

cleanupEnv := tests.SetupLocalGitVcsEnv(t)
defer cleanupEnv()

testDir := tests.CopyVcsGitFixture(t, tests.Temp)
targetPath := tests.RtRepo1 + "/local-git-vcs-nested/"

runRt(t, "upload", filepath.Join(testDir, "OtherGit", "*.in"), targetPath, "--flat=true")

resultItems := searchItemsInArtifactory(t, tests.SearchRepo1ByInSuffix)
var uploaded []rtutils.ResultItem
for _, item := range resultItems {
if item.Name == "b1.in" || item.Name == "b2.in" {
uploaded = append(uploaded, item)
}
}
assert.Len(t, uploaded, 2)

tests.ValidateLocalGitVcsPropsOnArtifacts(t, uploaded,
tests.VcsFixtureOtherURL, tests.VcsFixtureOtherRevision, tests.VcsFixtureOtherBranch)

cleanArtifactoryTest()
}

func TestUploadUserPropsOverrideLocalGitVcs(t *testing.T) {
initArtifactoryTest(t, "")

cleanupEnv := tests.SetupLocalGitVcsEnv(t)
defer cleanupEnv()

testDir := tests.CopyVcsGitFixture(t, tests.Temp)
targetPath := tests.RtRepo1 + "/local-git-vcs-user-override/"
userProps := "vcs.url=https://example.com/custom.git;vcs.revision=deadbeef;vcs.branch=feature-x"

runRt(t, "upload", filepath.Join(testDir, "a1.in"), targetPath, "--flat=true", "--target-props="+userProps)

serviceManager, err := utils.CreateServiceManager(serverDetails, 3, 1000, false)
require.NoError(t, err)

props, err := serviceManager.GetItemProps(targetPath + "a1.in")
require.NoError(t, err)
require.Contains(t, props.Properties["vcs.url"], "https://example.com/custom.git")
require.Contains(t, props.Properties["vcs.revision"], "deadbeef")
require.Contains(t, props.Properties["vcs.branch"], "feature-x")
require.NotContains(t, props.Properties["vcs.url"], tests.VcsFixtureMainURL)
require.NotContains(t, props.Properties["vcs.revision"], tests.VcsFixtureMainRevision)

cleanArtifactoryTest()
}

// TestUploadCIPlusLocalGitVcsProps verifies CI provides provider/org/repo
// while local git fills url/revision/branch when CI env lacks them.
func TestUploadCIPlusLocalGitVcsProps(t *testing.T) {
initArtifactoryTest(t, "")

cleanupEnv, actualOrg, actualRepo := tests.SetupGitHubActionsEnvForLocalGitMerge(t)
defer cleanupEnv()

testDir := tests.CopyVcsGitFixture(t, tests.Temp)
targetPath := tests.RtRepo1 + "/local-git-vcs-ci-merge/"

runRt(t, "upload", filepath.Join(testDir, "a1.in"), targetPath, "--flat=true")

serviceManager, err := utils.CreateServiceManager(serverDetails, 3, 1000, false)
require.NoError(t, err)

props, err := serviceManager.GetItemProps(targetPath + "a1.in")
require.NoError(t, err)

require.Contains(t, props.Properties["vcs.provider"], "github")
require.Contains(t, props.Properties["vcs.org"], actualOrg)
require.Contains(t, props.Properties["vcs.repo"], actualRepo)
require.Contains(t, props.Properties["vcs.url"], tests.VcsFixtureMainURL)
require.Contains(t, props.Properties["vcs.revision"], tests.VcsFixtureMainRevision)
require.Contains(t, props.Properties["vcs.branch"], tests.VcsFixtureMainBranch)

cleanArtifactoryTest()
}

func TestUploadNoLocalGitVcsWhenNoGitRepo(t *testing.T) {
initArtifactoryTest(t, "")

cleanupEnv := tests.SetupLocalGitVcsEnv(t)
defer cleanupEnv()

tmpDir, cleanupTmp := coretests.CreateTempDirWithCallbackAndAssert(t)
defer cleanupTmp()

filePath := filepath.Join(tmpDir, "no-git.txt")
require.NoError(t, os.WriteFile(filePath, []byte("no git here"), 0644))

targetPath := tests.RtRepo1 + "/local-git-vcs-none/"
runRt(t, "upload", filePath, targetPath, "--flat=true")

resultItems := searchItemsInArtifactory(t, tests.SearchAllRepo1)
var uploaded []rtutils.ResultItem
for _, item := range resultItems {
if item.Name == "no-git.txt" {
uploaded = append(uploaded, item)
}
}
require.Len(t, uploaded, 1)
tests.ValidateNoLocalGitVcsPropsOnArtifacts(t, uploaded)

cleanArtifactoryTest()
}
36 changes: 36 additions & 0 deletions buildinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1234,6 +1234,42 @@ func TestBuildPublishWithCIVcsProps(t *testing.T) {
cleanArtifactoryTest()
}

// TestBuildPublishWithLocalGitVcsProps verifies build-publish sets local git VCS props
// when CI env is absent but VCS collection is enabled.
func TestBuildPublishWithLocalGitVcsProps(t *testing.T) {
initArtifactoryTest(t, "")
buildName := tests.RtBuildName1 + "-local-git"
buildNumber := "1"

cleanupEnv := tests.SetupLocalGitVcsEnv(t)
defer cleanupEnv()

inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails)
defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails)

testDir := tests.CopyVcsGitFixture(t, tests.Temp)
runRt(t, "upload", filepath.Join(testDir, "a1.in"), tests.RtRepo1+"/local-git-bp/", "--flat=true",
"--build-name="+buildName, "--build-number="+buildNumber)

runRt(t, "build-publish", buildName, buildNumber, "--dot-git-path", testDir)

resultItems := getResultItemsFromArtifactory(tests.SearchAllRepo1, t)
require.Greater(t, len(resultItems), 0)

var uploaded []rtutils.ResultItem
for _, item := range resultItems {
if item.Name == "a1.in" {
uploaded = append(uploaded, item)
}
}
require.NotEmpty(t, uploaded)

tests.ValidateLocalGitVcsPropsOnArtifacts(t, uploaded,
tests.VcsFixtureMainURL, tests.VcsFixtureMainRevision, tests.VcsFixtureMainBranch)

cleanArtifactoryTest()
}

// TestBuildPublishWithoutCI tests that CI VCS properties are NOT set on artifacts
// when running build-publish outside of a CI environment.
func TestBuildPublishWithoutCI(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ require (
sigs.k8s.io/yaml v1.6.0 // indirect
)

// replace github.com/jfrog/jfrog-cli-artifactory => github.com/jfrog/jfrog-cli-artifactory main

//replace github.com/gfleury/go-bitbucket-v1 => github.com/gfleury/go-bitbucket-v1 v0.0.0-20230825095122-9bc1711434ab

//replace github.com/ktrysmt/go-bitbucket => github.com/ktrysmt/go-bitbucket v0.9.80
Expand Down
88 changes: 88 additions & 0 deletions utils/tests/artifact_props.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package tests

import (
"strings"
"testing"

buildinfo "github.com/jfrog/build-info-go/entities"
"github.com/jfrog/jfrog-client-go/artifactory"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// ArtifactFullPath builds the Artifactory item path for GetItemProps.
// When OriginalDeploymentRepo is empty (common with Gradle extractor build-info),
// defaultRepo is used as the repository prefix.
func ArtifactFullPath(a buildinfo.Artifact, defaultRepo string) string {
path := strings.TrimPrefix(a.Path, "/")
repo := a.OriginalDeploymentRepo
if repo == "" {
repo = defaultRepo
}
if repo != "" {
return repo + "/" + path
}
return path
}

// ArtifactItemPath returns the Artifactory item path for GetItemProps.
// When Name is set and not already part of Path (e.g. UV stores Path as a directory),
// Name is appended as the filename segment.
func ArtifactItemPath(a buildinfo.Artifact, defaultRepo string) string {
fullPath := ArtifactFullPath(a, defaultRepo)
if a.Name == "" {
return fullPath
}
if strings.HasSuffix(fullPath, "/"+a.Name) || strings.HasSuffix(fullPath, a.Name) {
return fullPath
}
return fullPath + "/" + a.Name
}

// ValidateLocalGitVcsPropsOnBuildInfoArtifacts fetches props for each build-info artifact
// and asserts local-git VCS fields. Returns the number of artifacts validated.
func ValidateLocalGitVcsPropsOnBuildInfoArtifacts(
t *testing.T,
serviceManager artifactory.ArtifactoryServicesManager,
publishedBuildInfo *buildinfo.PublishedBuildInfo,
defaultRepo string,
expectedURL, expectedRevision, expectedBranch string,
) int {
t.Helper()
require.NotNil(t, publishedBuildInfo)

count := 0
for _, module := range publishedBuildInfo.BuildInfo.Modules {
for _, artifact := range module.Artifacts {
fullPath := ArtifactItemPath(artifact, defaultRepo)
if fullPath == "" {
continue
}

props, err := serviceManager.GetItemProps(fullPath)
require.NoError(t, err, "GetItemProps failed for %s", fullPath)
if props == nil {
assert.Fail(t, "Properties are nil for artifact: %s", fullPath)
continue
}

assert.Contains(t, props.Properties, "vcs.url", "Missing vcs.url on %s", artifact.Name)
assert.Contains(t, props.Properties["vcs.url"], expectedURL, "Wrong vcs.url on %s", artifact.Name)

assert.Contains(t, props.Properties, "vcs.revision", "Missing vcs.revision on %s", artifact.Name)
assert.Contains(t, props.Properties["vcs.revision"], expectedRevision, "Wrong vcs.revision on %s", artifact.Name)

if expectedBranch != "" {
assert.Contains(t, props.Properties, "vcs.branch", "Missing vcs.branch on %s", artifact.Name)
assert.Contains(t, props.Properties["vcs.branch"], expectedBranch, "Wrong vcs.branch on %s", artifact.Name)
}

// Local-git-only: provider/org/repo must NOT appear when CI is cleared
_, hasProvider := props.Properties["vcs.provider"]
assert.False(t, hasProvider, "vcs.provider should not be set on %s in local-git-only mode", artifact.Name)

count++
}
}
return count
}
57 changes: 57 additions & 0 deletions utils/tests/artifact_props_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package tests

import (
"testing"

buildinfo "github.com/jfrog/build-info-go/entities"
"github.com/stretchr/testify/assert"
)

func TestArtifactFullPath(t *testing.T) {
t.Run("uses OriginalDeploymentRepo when set", func(t *testing.T) {
a := buildinfo.Artifact{OriginalDeploymentRepo: "cli-gradle-123", Path: "com/foo/1.0/foo.jar"}
assert.Equal(t, "cli-gradle-123/com/foo/1.0/foo.jar", ArtifactFullPath(a, "fallback-repo"))
})

t.Run("falls back to defaultRepo when OriginalDeploymentRepo empty", func(t *testing.T) {
a := buildinfo.Artifact{Path: "com/foo/1.0/foo.jar"}
assert.Equal(t, "cli-gradle-123/com/foo/1.0/foo.jar", ArtifactFullPath(a, "cli-gradle-123"))
})

t.Run("falls back to Path when repo empty and no default", func(t *testing.T) {
a := buildinfo.Artifact{Path: "com/foo/1.0/foo.jar"}
assert.Equal(t, "com/foo/1.0/foo.jar", ArtifactFullPath(a, ""))
})

t.Run("strips leading slash from Path", func(t *testing.T) {
a := buildinfo.Artifact{Path: "/minimal-example/1.0/minimal-example-1.0.jar"}
assert.Equal(t, "cli-gradle-123/minimal-example/1.0/minimal-example-1.0.jar", ArtifactFullPath(a, "cli-gradle-123"))
})
}

func TestValidateLocalGitVcsPropsOnBuildInfoArtifacts_UsesArtifactFullPath(t *testing.T) {
// Smoke-test ArtifactFullPath integration used by the helper (no Artifactory call).
a := buildinfo.Artifact{
OriginalDeploymentRepo: "",
Path: "/com/foo/1.0/foo.jar",
}
assert.Equal(t, "my-repo/com/foo/1.0/foo.jar", ArtifactFullPath(a, "my-repo"))
}

func TestArtifactItemPath_AppendsNameForDirectoryPath(t *testing.T) {
a := buildinfo.Artifact{
OriginalDeploymentRepo: "uv-local",
Path: "my-pkg/0.1.0",
Name: "my_pkg-0.1.0-py3-none-any.whl",
}
assert.Equal(t, "uv-local/my-pkg/0.1.0/my_pkg-0.1.0-py3-none-any.whl", ArtifactItemPath(a, ""))
}

func TestArtifactItemPath_DoesNotDoubleAppendName(t *testing.T) {
a := buildinfo.Artifact{
OriginalDeploymentRepo: "mvn-local",
Path: "com/foo/1.0/foo.jar",
Name: "foo.jar",
}
assert.Equal(t, "mvn-local/com/foo/1.0/foo.jar", ArtifactItemPath(a, ""))
}
Loading
Loading