Skip to content

Commit 29d810b

Browse files
committed
Updated fix to use permissions based 403 instead of 404
1 parent 37ec50f commit 29d810b

File tree

2 files changed

+140
-121
lines changed

2 files changed

+140
-121
lines changed

artifactory_test.go

Lines changed: 78 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -2801,146 +2801,89 @@ func TestArtifactoryDeleteByProps(t *testing.T) {
28012801
cleanArtifactoryTest()
28022802
}
28032803

2804-
// TestArtifactoryDeleteCountsNonExistentFiles tests that delete correctly handles
2805-
// files that don't exist. The CLI searches first, so non-existent files return
2806-
// All 3 files should return 404 on second delete attempt.
2807-
// TestArtifactoryDeleteCountsNonExistentFiles tests that delete correctly counts
2808-
// 404 failures when all files have already been deleted.
2809-
// Files are placed in separate subfolders to prevent path reduction optimization.
2810-
func TestArtifactoryDeleteCountsNonExistentFiles(t *testing.T) {
2811-
initArtifactoryTest(t, "")
2812-
2813-
// Step 1: Upload 3 specific test files in SEPARATE subfolders to prevent path reduction
2814-
// Each file goes in its own subfolder so delete service can't optimize to folder delete
2815-
testFiles := []string{"f1/a1.in", "f2/a2.in", "f3/a3.in"}
2816-
sourceFiles := []string{"testdata/a/a1.in", "testdata/a/a2.in", "testdata/a/a3.in"}
2817-
for i, f := range testFiles {
2818-
runRt(t, "upload", sourceFiles[i], tests.RtRepo1+"/delete-nonexistent/"+f)
2819-
}
2820-
t.Logf("Uploaded %d test files: %v", len(testFiles), testFiles)
2821-
2822-
// Step 2: Delete each file using direct DELETE API and verify success (HTTP 204)
2823-
for _, f := range testFiles {
2824-
artifactPath := tests.RtRepo1 + "/delete-nonexistent/" + f
2825-
success, err := tests.DeleteFileDirect(serverDetails, artifactPath)
2826-
assert.NoError(t, err, "First delete of %s should not return error", f)
2827-
assert.True(t, success, "First delete of %s should succeed (HTTP 204)", f)
2828-
}
2829-
t.Log("First delete: all files deleted successfully (HTTP 204)")
2830-
2831-
// Step 3: Verify all files are now gone via search
2832-
searchSpec := spec.NewBuilder().
2833-
Pattern(tests.RtRepo1 + "/delete-nonexistent/*").
2834-
Recursive(true).
2835-
BuildSpec()
2836-
searchCmd := generic.NewSearchCommand()
2837-
searchCmd.SetServerDetails(serverDetails).SetSpec(searchSpec)
2838-
reader, err := searchCmd.Search()
2839-
assert.NoError(t, err)
2840-
remainingFiles, err := reader.Length()
2841-
assert.NoError(t, err)
2842-
readerCloseAndAssert(t, reader)
2843-
assert.Equal(t, 0, remainingFiles, "All files should be deleted")
2844-
t.Log("Verified all files are deleted")
2804+
// TestArtifactoryDeleteCountsPartialSuccess tests that delete correctly handles
2805+
// a mix of successful and failed deletions using CLI's DeleteFiles API.
2806+
// Uses permission-based failures (403) to reliably test partial success scenarios.
2807+
// - Files in RtRepo1: user has delete permission → success (204)
2808+
// - Files in RtRepo2: user has NO delete permission → failure (403)
2809+
func TestArtifactoryDeleteCountsPartialSuccess(t *testing.T) {
2810+
initArtifactoryTest(t, "")
28452811

2846-
// Step 4: Try to delete ALL files AGAIN using CLI's DeleteFiles API
2847-
// Build list of artifact paths
2848-
var artifactPaths []string
2849-
for _, f := range testFiles {
2850-
artifactPaths = append(artifactPaths, tests.RtRepo1+"/delete-nonexistent/"+f)
2851-
}
2852-
t.Logf("Attempting second delete of paths: %v", artifactPaths)
2812+
// Test user credentials
2813+
testUser := "delete-test-user"
2814+
testPassword := "DeleteTest123!"
28532815

2854-
// Use CLI's DeleteFiles API which should properly count 404s as failures
2855-
// Error IS expected because all files return 404
2856-
totalSuccess, totalFail, err := tests.DeleteFilesByPathsUsingCli(serverDetails, artifactPaths)
2857-
// Error is expected when files don't exist (404 responses)
2858-
t.Logf("Second delete result: success=%d, fail=%d, err=%v", totalSuccess, totalFail, err)
2816+
// Step 1: Create a test user
2817+
err := tests.CreateUserWithPassword(serverDetails, testUser, testPassword)
2818+
assert.NoError(t, err, "Failed to create test user")
2819+
t.Logf("Created test user: %s", testUser)
28592820

2860-
// Step 5: Verify - all 3 files should return 404 (failedCount=3)
2861-
expectedSuccess := 0
2862-
expectedFail := len(testFiles)
2863-
t.Logf("Expected: successCount=%d, failCount=%d", expectedSuccess, expectedFail)
2864-
t.Logf("Actual: successCount=%d, failCount=%d", totalSuccess, totalFail)
2865-
assert.Equal(t, expectedSuccess, totalSuccess,
2866-
"Second delete should have 0 successes (all files already deleted), got %d", totalSuccess)
2867-
assert.Equal(t, expectedFail, totalFail,
2868-
"Second delete should have %d failures (all 404), got %d", expectedFail, totalFail)
2821+
// Cleanup user at the end
2822+
defer func() {
2823+
_ = tests.DeleteUser(serverDetails, testUser)
2824+
t.Logf("Cleaned up test user: %s", testUser)
2825+
}()
28692826

2870-
// Cleanup
2871-
cleanArtifactoryTest()
2872-
}
2827+
// Step 2: Create permission target - user can delete from RtRepo1 but NOT from RtRepo2
2828+
permissionName := "delete-partial-test-perm"
2829+
err = tests.CreatePermissionTarget(serverDetails, permissionName, tests.RtRepo1, testUser, []string{"read", "write", "delete"})
2830+
assert.NoError(t, err, "Failed to create permission target for RtRepo1")
2831+
t.Logf("Created permission target: %s (allows delete on %s)", permissionName, tests.RtRepo1)
28732832

2874-
// TestArtifactoryDeleteCountsPartiallyDeleted tests that delete correctly handles
2875-
// a mix of existing and non-existing files using CLI's DeleteFiles API.
2876-
// - 3 existing files should return success
2877-
// - 2 pre-deleted files should return failure (404)
2878-
// Files are placed in separate subfolders to prevent path reduction optimization.
2879-
func TestArtifactoryDeleteCountsPartiallyDeleted(t *testing.T) {
2880-
initArtifactoryTest(t, "")
2833+
// Cleanup permission at the end
2834+
defer func() {
2835+
_ = tests.DeletePermissionTarget(serverDetails, permissionName)
2836+
t.Logf("Cleaned up permission target: %s", permissionName)
2837+
}()
28812838

2882-
// Step 1: Upload 5 specific test files in SEPARATE subfolders to prevent path reduction
2883-
// Each file goes in its own subfolder so delete service can't optimize to folder delete
2884-
testFiles := []string{"f1/a1.in", "f2/a2.in", "f3/a3.in", "f4/b1.in", "f5/b2.in"}
2885-
sourceFiles := []string{"testdata/a/a1.in", "testdata/a/a2.in", "testdata/a/a3.in", "testdata/a/b/b1.in", "testdata/a/b/b2.in"}
2886-
for i, f := range testFiles {
2887-
runRt(t, "upload", sourceFiles[i], tests.RtRepo1+"/delete-partial/"+f)
2839+
// Step 3: Upload files as admin to both repos
2840+
// 3 files to RtRepo1 (will succeed with delete)
2841+
allowedFiles := []string{"f1/a1.in", "f2/a2.in", "f3/a3.in"}
2842+
sourceFilesAllowed := []string{"testdata/a/a1.in", "testdata/a/a2.in", "testdata/a/a3.in"}
2843+
for i, f := range allowedFiles {
2844+
runRt(t, "upload", sourceFilesAllowed[i], tests.RtRepo1+"/delete-partial/"+f)
28882845
}
2889-
t.Logf("Uploaded %d test files: %v", len(testFiles), testFiles)
2846+
t.Logf("Uploaded %d files to %s (delete allowed)", len(allowedFiles), tests.RtRepo1)
28902847

2891-
// Step 2: Pre-delete 2 files (f1/a1.in and f2/a2.in) using direct DELETE API
2892-
filesToPreDelete := []string{"f1/a1.in", "f2/a2.in"}
2893-
for _, f := range filesToPreDelete {
2894-
artifactPath := tests.RtRepo1 + "/delete-partial/" + f
2895-
success, err := tests.DeleteFileDirect(serverDetails, artifactPath)
2896-
assert.NoError(t, err, "Pre-delete of %s should not return error", f)
2897-
assert.True(t, success, "Pre-delete of %s should succeed (HTTP 204)", f)
2898-
t.Logf("Pre-deleted %s successfully (HTTP 204)", f)
2848+
// 2 files to RtRepo2 (will fail with 403)
2849+
deniedFiles := []string{"f4/b1.in", "f5/b2.in"}
2850+
sourceFilesDenied := []string{"testdata/a/b/b1.in", "testdata/a/b/b2.in"}
2851+
for i, f := range deniedFiles {
2852+
runRt(t, "upload", sourceFilesDenied[i], tests.RtRepo2+"/delete-partial/"+f)
28992853
}
2854+
t.Logf("Uploaded %d files to %s (delete denied)", len(deniedFiles), tests.RtRepo2)
29002855

2901-
// Step 3: Verify pre-deleted files are gone via search
2902-
for _, f := range filesToPreDelete {
2903-
searchSpec := spec.NewBuilder().
2904-
Pattern(tests.RtRepo1 + "/delete-partial/" + f).
2905-
BuildSpec()
2906-
searchCmd := generic.NewSearchCommand()
2907-
searchCmd.SetServerDetails(serverDetails).SetSpec(searchSpec)
2908-
reader, err := searchCmd.Search()
2909-
assert.NoError(t, err)
2910-
count, err := reader.Length()
2911-
assert.NoError(t, err)
2912-
readerCloseAndAssert(t, reader)
2913-
assert.Equal(t, 0, count, "File %s should be deleted", f)
2914-
}
2915-
t.Log("Verified pre-deleted files are gone")
2856+
// Step 4: Create server details for the test user
2857+
testUserServerDetails := tests.CreateServerDetailsWithCredentials(serverDetails, testUser, testPassword)
29162858

2917-
// Step 4: Now try to delete ALL 5 files using CLI's DeleteFiles API
2918-
// - 3 files exist (f3/a3, f4/b1, f5/b2) → should return success
2919-
// - 2 files don't exist (f1/a1, f2/a2) → should return failure (404)
2859+
// Step 5: Build list of all artifact paths (from both repos)
29202860
var artifactPaths []string
2921-
for _, f := range testFiles {
2861+
for _, f := range allowedFiles {
29222862
artifactPaths = append(artifactPaths, tests.RtRepo1+"/delete-partial/"+f)
29232863
}
2924-
t.Logf("Attempting to delete paths: %v", artifactPaths)
2864+
for _, f := range deniedFiles {
2865+
artifactPaths = append(artifactPaths, tests.RtRepo2+"/delete-partial/"+f)
2866+
}
2867+
t.Logf("Attempting to delete %d paths with limited user: %v", len(artifactPaths), artifactPaths)
29252868

2926-
// Error IS expected because some files return 404
2927-
totalSuccess, totalFail, err := tests.DeleteFilesByPathsUsingCli(serverDetails, artifactPaths)
2928-
// Error is expected when some files don't exist (404 responses)
2929-
t.Logf("Delete all 5 files result: success=%d, fail=%d, err=%v", totalSuccess, totalFail, err)
2869+
// Step 6: Delete using the test user's credentials
2870+
// - 3 files from RtRepo1 → should succeed (204)
2871+
// - 2 files from RtRepo2 → should fail (403)
2872+
totalSuccess, totalFail, err := tests.DeleteFilesByPathsUsingCli(testUserServerDetails, artifactPaths)
2873+
// Error IS expected because some files return 403
2874+
t.Logf("Delete result: success=%d, fail=%d, err=%v", totalSuccess, totalFail, err)
29302875

2931-
// Step 5: Verify counts
2932-
// 3 files existed (f3/a3, f4/b1, f5/b2) = 3 success
2933-
// 2 files didn't exist (f1/a1, f2/a2) = 2 fail (404)
2934-
expectedSuccess := len(testFiles) - len(filesToPreDelete)
2935-
expectedFail := len(filesToPreDelete)
2876+
// Step 7: Verify counts
2877+
expectedSuccess := len(allowedFiles)
2878+
expectedFail := len(deniedFiles)
29362879
t.Logf("Expected: successCount=%d, failCount=%d", expectedSuccess, expectedFail)
29372880
t.Logf("Actual: successCount=%d, failCount=%d", totalSuccess, totalFail)
29382881
assert.Equal(t, expectedSuccess, totalSuccess,
2939-
"Should have %d successes (f3/a3, f4/b1, f5/b2 still existed), got %d", expectedSuccess, totalSuccess)
2882+
"Should have %d successes (files from %s), got %d", expectedSuccess, tests.RtRepo1, totalSuccess)
29402883
assert.Equal(t, expectedFail, totalFail,
2941-
"Should have %d failures (f1/a1, f2/a2 already deleted = 404), got %d", expectedFail, totalFail)
2884+
"Should have %d failures (files from %s - 403), got %d", expectedFail, tests.RtRepo2, totalFail)
29422885

2943-
// Step 6: Verify all files are now gone
2886+
// Step 8: Verify files from RtRepo1 are deleted
29442887
searchSpec := spec.NewBuilder().
29452888
Pattern(tests.RtRepo1 + "/delete-partial/*").
29462889
Recursive(true).
@@ -2949,10 +2892,24 @@ func TestArtifactoryDeleteCountsPartiallyDeleted(t *testing.T) {
29492892
searchCmd.SetServerDetails(serverDetails).SetSpec(searchSpec)
29502893
reader, err := searchCmd.Search()
29512894
assert.NoError(t, err)
2952-
finalCount, err := reader.Length()
2895+
repo1Count, err := reader.Length()
29532896
assert.NoError(t, err)
29542897
readerCloseAndAssert(t, reader)
2955-
assert.Equal(t, 0, finalCount, "All files should be deleted")
2898+
assert.Equal(t, 0, repo1Count, "All files from %s should be deleted", tests.RtRepo1)
2899+
2900+
// Step 9: Verify files from RtRepo2 still exist (delete failed)
2901+
searchSpec2 := spec.NewBuilder().
2902+
Pattern(tests.RtRepo2 + "/delete-partial/*").
2903+
Recursive(true).
2904+
BuildSpec()
2905+
searchCmd2 := generic.NewSearchCommand()
2906+
searchCmd2.SetServerDetails(serverDetails).SetSpec(searchSpec2)
2907+
reader2, err := searchCmd2.Search()
2908+
assert.NoError(t, err)
2909+
repo2Count, err := reader2.Length()
2910+
assert.NoError(t, err)
2911+
readerCloseAndAssert(t, reader2)
2912+
assert.Equal(t, len(deniedFiles), repo2Count, "Files from %s should still exist (delete was denied)", tests.RtRepo2)
29562913

29572914
// Cleanup
29582915
cleanArtifactoryTest()

utils/tests/utils.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,68 @@ func DeleteFilesByPathsUsingCli(serverDetails *config.ServerDetails, artifactory
347347
return deleteCmd.DeleteFiles(reader)
348348
}
349349

350+
// CreateUserWithPassword creates a new user with the specified password.
351+
func CreateUserWithPassword(serverDetails *config.ServerDetails, username, password string) error {
352+
servicesManager, err := artUtils.CreateServiceManager(serverDetails, -1, 0, false)
353+
if err != nil {
354+
return err
355+
}
356+
adminFalse := false
357+
userParams := services.NewUserParams()
358+
userParams.UserDetails.Name = username
359+
userParams.UserDetails.Email = username + "@test.com"
360+
userParams.UserDetails.Password = password
361+
userParams.UserDetails.Admin = &adminFalse
362+
return servicesManager.CreateUser(userParams)
363+
}
364+
365+
// DeleteUser deletes a user.
366+
func DeleteUser(serverDetails *config.ServerDetails, username string) error {
367+
servicesManager, err := artUtils.CreateServiceManager(serverDetails, -1, 0, false)
368+
if err != nil {
369+
return err
370+
}
371+
return servicesManager.DeleteUser(username)
372+
}
373+
374+
// CreatePermissionTarget creates a permission target giving the specified user permissions on the specified repo.
375+
func CreatePermissionTarget(serverDetails *config.ServerDetails, permName, repoKey, username string, actions []string) error {
376+
servicesManager, err := artUtils.CreateServiceManager(serverDetails, -1, 0, false)
377+
if err != nil {
378+
return err
379+
}
380+
params := services.NewPermissionTargetParams()
381+
params.Name = permName
382+
params.Repo = &services.PermissionTargetSection{
383+
Repositories: []string{repoKey},
384+
Actions: &services.Actions{
385+
Users: map[string][]string{
386+
username: actions,
387+
},
388+
},
389+
}
390+
return servicesManager.CreatePermissionTarget(params)
391+
}
392+
393+
// DeletePermissionTarget deletes a permission target.
394+
func DeletePermissionTarget(serverDetails *config.ServerDetails, permName string) error {
395+
servicesManager, err := artUtils.CreateServiceManager(serverDetails, -1, 0, false)
396+
if err != nil {
397+
return err
398+
}
399+
return servicesManager.DeletePermissionTarget(permName)
400+
}
401+
402+
// CreateServerDetailsWithCredentials creates a copy of server details with different credentials.
403+
func CreateServerDetailsWithCredentials(original *config.ServerDetails, username, password string) *config.ServerDetails {
404+
newDetails := *original
405+
newDetails.SetUser(username)
406+
newDetails.SetPassword(password)
407+
// Clear access token if using username/password
408+
newDetails.SetAccessToken("")
409+
return &newDetails
410+
}
411+
350412
// This function makes no assertion, caller is responsible to assert as needed.
351413
func GetBuildInfo(serverDetails *config.ServerDetails, buildName, buildNumber string) (pbi *buildinfo.PublishedBuildInfo, found bool, err error) {
352414
servicesManager, err := artUtils.CreateServiceManager(serverDetails, -1, 0, false)

0 commit comments

Comments
 (0)