From 96190838fd383d83de5b12e4e3dee73192114466 Mon Sep 17 00:00:00 2001 From: Ompragash Viswanathan Date: Tue, 7 Oct 2025 18:42:49 +0530 Subject: [PATCH] fix --- plugin/plugin.go | 96 +++++++++- plugin/plugin_test.go | 420 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 511 insertions(+), 5 deletions(-) diff --git a/plugin/plugin.go b/plugin/plugin.go index f5268b1..ecc69f3 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -13,6 +13,7 @@ import ( "mime/multipart" "net/http" "os" + "path/filepath" "regexp" "strings" ) @@ -153,7 +154,17 @@ func (n *NexusPlugin) Run() error { n.HttpClient = &http.Client{} } - for _, artifact := range n.Artifacts { + // Log upload configuration summary + LogPrintln(n, "") + LogPrintln(n, "Upload Configuration:") + LogPrintln(n, fmt.Sprintf(" Nexus Version: %s", n.Version)) + LogPrintln(n, fmt.Sprintf(" Server URL: %s", n.ServerUrl)) + LogPrintln(n, fmt.Sprintf(" Repository: %s", n.Repository)) + LogPrintln(n, fmt.Sprintf(" Format: %s", n.Format)) + LogPrintln(n, fmt.Sprintf(" Total artifacts: %d", len(n.Artifacts))) + LogPrintln(n, "") + + for idx, artifact := range n.Artifacts { filePath := artifact.File file, err := os.Open(filePath) if err != nil { @@ -161,6 +172,28 @@ func (n *NexusPlugin) Run() error { continue } + // Log individual artifact details before upload + LogPrintln(n, fmt.Sprintf("Uploading artifact %d/%d:", idx+1, len(n.Artifacts))) + + // Get file size from the opened file handle + fileInfo, statErr := file.Stat() + var sizeStr string + if statErr == nil { + fileSize := float64(fileInfo.Size()) / (1024 * 1024) // Convert to MB + sizeStr = fmt.Sprintf(" (%.2f MB)", fileSize) + } + LogPrintln(n, fmt.Sprintf(" File: %s%s", filePath, sizeStr)) + + LogPrintln(n, fmt.Sprintf(" ArtifactId: %s", artifact.ArtifactId)) + if artifact.GroupId != "" { + LogPrintln(n, fmt.Sprintf(" GroupId: %s", artifact.GroupId)) + } + LogPrintln(n, fmt.Sprintf(" Version: %s", artifact.Version)) + LogPrintln(n, fmt.Sprintf(" Type: %s", artifact.Type)) + if artifact.Classifier != "" { + LogPrintln(n, fmt.Sprintf(" Classifier: %s", artifact.Classifier)) + } + if n.Version == "nexus2" { artifactURL := n.prepareNexus2ArtifactURL(artifact) if err := n.uploadFileNexus2(artifactURL, file, filePath); err != nil { @@ -186,9 +219,23 @@ func (n *NexusPlugin) Run() error { LogPrintln(n, "Error closing file: ", err.Error()) } - fmt.Println("Successfully uploaded artifact:", filePath) + // Log enhanced success message with artifact coordinates + basename := filepath.Base(filePath) + coordinates := fmt.Sprintf("%s:%s:%s", artifact.GroupId, artifact.ArtifactId, artifact.Version) + if artifact.GroupId == "" { + coordinates = fmt.Sprintf("%s:%s", artifact.ArtifactId, artifact.Version) + } + LogPrintln(n, fmt.Sprintf("[OK] Successfully uploaded: %s -> %s", basename, coordinates)) + LogPrintln(n, "") } + // Log upload summary + totalArtifacts := len(n.Artifacts) + successCount := totalArtifacts - len(n.Failed) + + LogPrintln(n, "Upload Summary:") + LogPrintln(n, fmt.Sprintf(" Total: %d, Successful: %d, Failed: %d", totalArtifacts, successCount, len(n.Failed))) + if len(n.Failed) > 0 { return GetNewError("NexusPlugin Error in Run: some artifacts failed to upload") } @@ -302,7 +349,9 @@ func (n *NexusPlugin) IsMultiFileUploadArgsOk(args Args) error { n.UserName = args.Username n.Password = args.Password n.Repository = args.Repository - n.ServerUrl = args.Protocol + "://" + args.ServerUrl + // Fix Bug #3: Remove trailing slashes from server URL before concatenating + serverUrl := strings.TrimRight(args.ServerUrl, "/") + n.ServerUrl = args.Protocol + "://" + serverUrl n.GroupId = args.GroupId n.Version = args.NexusVersion n.Format = args.Format @@ -382,7 +431,8 @@ func (n *NexusPlugin) IsSingleFileUploadArgsOk(args Args) error { n.UserName = args.Username n.Password = args.Password n.Repository = args.Repository - n.ServerUrl = args.ServerUrl + // Fix Bug #3: Remove trailing slashes from server URL + n.ServerUrl = strings.TrimRight(args.ServerUrl, "/") n.Format = args.Format n.GroupId = values["CgroupId"] n.Version = "nexus3" @@ -456,10 +506,27 @@ func (n *NexusPlugin) uploadFileNexus2(url string, content io.Reader, filePath s } defer resp.Body.Close() + // Fix Bug #1: Read response body to provide detailed error messages + bodyBytes, readErr := io.ReadAll(resp.Body) + var bodyContent string + if readErr == nil { + bodyContent = string(bodyBytes) + } + if resp.StatusCode >= 400 { fmt.Println("File upload failed status ", resp.StatusCode) + if bodyContent != "" { + fmt.Println("Response body: ", bodyContent) + return fmt.Errorf("Upload failed with status %d: %s", resp.StatusCode, bodyContent) + } return fmt.Errorf("Upload failed with status %d", resp.StatusCode) } + + // Log success response body for debugging + if bodyContent != "" { + fmt.Println("Upload successful. Response: ", bodyContent) + } + return nil } @@ -487,7 +554,10 @@ func (n *NexusPlugin) uploadFileNexus3(artifact Artifact, filePath string) error assetFieldName = fmt.Sprintf("%s.asset", n.Format) } - fileWriter, err := writer.CreateFormFile(assetFieldName, artifact.File) + // Fix Bug #2: Extract basename from file path to avoid sending full paths to Nexus + // This handles both Linux (/path/to/file.jar) and Windows (C:\path\to\file.jar) paths + basename := filepath.Base(artifact.File) + fileWriter, err := writer.CreateFormFile(assetFieldName, basename) if err != nil { LogPrintln(n, "Error CreateFormFile: ", err.Error()) return err @@ -528,11 +598,27 @@ func (n *NexusPlugin) uploadFileNexus3(artifact Artifact, filePath string) error } defer resp.Body.Close() + // Fix Bug #1: Read response body to provide detailed error messages + bodyBytes, readErr := io.ReadAll(resp.Body) + var bodyContent string + if readErr == nil { + bodyContent = string(bodyBytes) + } + if resp.StatusCode >= 400 { LogPrintln(n, "Error upload failed with status: ", resp.StatusCode) + if bodyContent != "" { + LogPrintln(n, "Response body: ", bodyContent) + return fmt.Errorf("Upload failed with status %d: %s", resp.StatusCode, bodyContent) + } return fmt.Errorf("Upload failed with status %d", resp.StatusCode) } + // Log success response body for debugging + if bodyContent != "" { + LogPrintln(n, "Upload successful. Response: ", bodyContent) + } + return nil } diff --git a/plugin/plugin_test.go b/plugin/plugin_test.go index 90eb8ed..58214c6 100644 --- a/plugin/plugin_test.go +++ b/plugin/plugin_test.go @@ -185,3 +185,423 @@ func TestNexusPlugin_Run_MultiFileUpload_Success(t *testing.T) { assert.Empty(t, plugin.Failed) mockClient.AssertExpectations(t) } + +// Test Bug #3: URL Trailing Slash - Single File Upload +func TestIsSingleFileUploadArgsOk_TrailingSlash(t *testing.T) { + args := Args{ + EnvPluginInputArgs: EnvPluginInputArgs{ + Username: "testUser", + Password: "testPass", + ServerUrl: "https://nexus.example.com/", + Filename: "testfile.jar", + Format: "maven2", + Repository: "repo", + Attributes: "-CgroupId=com.test -CartifactId=app -Cversion=1.0.0 -Aextension=jar -Aclassifier=bin", + }, + } + + plugin := NexusPlugin{} + err := plugin.IsSingleFileUploadArgsOk(args) + + assert.Nil(t, err) + assert.Equal(t, "https://nexus.example.com", plugin.ServerUrl, "Trailing slash should be removed") +} + +// Test Bug #3: URL Multiple Trailing Slashes - Single File Upload +func TestIsSingleFileUploadArgsOk_MultipleTrailingSlashes(t *testing.T) { + args := Args{ + EnvPluginInputArgs: EnvPluginInputArgs{ + Username: "testUser", + Password: "testPass", + ServerUrl: "https://nexus.example.com///", + Filename: "testfile.jar", + Format: "maven2", + Repository: "repo", + Attributes: "-CgroupId=com.test -CartifactId=app -Cversion=1.0.0 -Aextension=jar -Aclassifier=bin", + }, + } + + plugin := NexusPlugin{} + err := plugin.IsSingleFileUploadArgsOk(args) + + assert.Nil(t, err) + assert.Equal(t, "https://nexus.example.com", plugin.ServerUrl, "Multiple trailing slashes should be removed") +} + +// Test Bug #3: URL No Trailing Slash - Single File Upload (should remain unchanged) +func TestIsSingleFileUploadArgsOk_NoTrailingSlash(t *testing.T) { + args := Args{ + EnvPluginInputArgs: EnvPluginInputArgs{ + Username: "testUser", + Password: "testPass", + ServerUrl: "https://nexus.example.com", + Filename: "testfile.jar", + Format: "maven2", + Repository: "repo", + Attributes: "-CgroupId=com.test -CartifactId=app -Cversion=1.0.0 -Aextension=jar -Aclassifier=bin", + }, + } + + plugin := NexusPlugin{} + err := plugin.IsSingleFileUploadArgsOk(args) + + assert.Nil(t, err) + assert.Equal(t, "https://nexus.example.com", plugin.ServerUrl, "URL without trailing slash should remain unchanged") +} + +// Test Bug #3: URL Trailing Slash - Multi File Upload +func TestIsMultiFileUploadArgsOk_TrailingSlash(t *testing.T) { + args := Args{ + EnvPluginInputArgs: EnvPluginInputArgs{ + Username: "testUser", + Password: "testPass", + Protocol: "https", + ServerUrl: "nexus.example.com/", + NexusVersion: "nexus3", + Repository: "repo", + GroupId: "com.test", + Format: "maven2", + Artifact: "[{\"file\":\"test.jar\",\"artifactId\":\"app\",\"type\":\"jar\",\"version\":\"1.0\",\"groupId\":\"com.test\"}]", + }, + } + + plugin := NexusPlugin{} + err := plugin.IsMultiFileUploadArgsOk(args) + + assert.Nil(t, err) + assert.Equal(t, "https://nexus.example.com", plugin.ServerUrl, "Trailing slash should be removed") +} + +// Test Bug #3: URL Multiple Trailing Slashes - Multi File Upload +func TestIsMultiFileUploadArgsOk_MultipleTrailingSlashes(t *testing.T) { + args := Args{ + EnvPluginInputArgs: EnvPluginInputArgs{ + Username: "testUser", + Password: "testPass", + Protocol: "https", + ServerUrl: "nexus.example.com///", + NexusVersion: "nexus3", + Repository: "repo", + GroupId: "com.test", + Format: "maven2", + Artifact: "[{\"file\":\"test.jar\",\"artifactId\":\"app\",\"type\":\"jar\",\"version\":\"1.0\",\"groupId\":\"com.test\"}]", + }, + } + + plugin := NexusPlugin{} + err := plugin.IsMultiFileUploadArgsOk(args) + + assert.Nil(t, err) + assert.Equal(t, "https://nexus.example.com", plugin.ServerUrl, "Multiple trailing slashes should be removed") +} + +// Test Bug #3: URL No Trailing Slash - Multi File Upload (should remain unchanged) +func TestIsMultiFileUploadArgsOk_NoTrailingSlash(t *testing.T) { + args := Args{ + EnvPluginInputArgs: EnvPluginInputArgs{ + Username: "testUser", + Password: "testPass", + Protocol: "https", + ServerUrl: "nexus.example.com", + NexusVersion: "nexus3", + Repository: "repo", + GroupId: "com.test", + Format: "maven2", + Artifact: "[{\"file\":\"test.jar\",\"artifactId\":\"app\",\"type\":\"jar\",\"version\":\"1.0\",\"groupId\":\"com.test\"}]", + }, + } + + plugin := NexusPlugin{} + err := plugin.IsMultiFileUploadArgsOk(args) + + assert.Nil(t, err) + assert.Equal(t, "https://nexus.example.com", plugin.ServerUrl, "URL without trailing slash should remain unchanged") +} + +// Test Bug #2: Filename extraction from absolute Linux path +func TestUploadFileNexus3_AbsolutePath_Linux(t *testing.T) { + mockClient := new(MockHttpClient) + mockResp := &http.Response{ + StatusCode: 200, + Body: ioutil.NopCloser(strings.NewReader("Success")), + } + + // Track what filename was actually sent in the multipart form + var capturedRequest *http.Request + mockClient.On("Do", mock.AnythingOfType("*http.Request")).Run(func(args mock.Arguments) { + capturedRequest = args.Get(0).(*http.Request) + }).Return(mockResp, nil) + + tmpFile, err := createTempFile("test content") + assert.NoError(t, err) + defer os.Remove(tmpFile) + + plugin := NexusPlugin{ + PluginProcessingInfo: PluginProcessingInfo{ + UserName: "testUser", + Password: "testPass", + ServerUrl: "https://nexus.example.com", + Repository: "repo", + Format: "maven2", + Version: "nexus3", + }, + HttpClient: mockClient, + } + + artifact := Artifact{ + File: tmpFile, + ArtifactId: "test-app", + Type: "jar", + Version: "1.0", + GroupId: "com.test", + } + + err = plugin.uploadFileNexus3(artifact, tmpFile) + + assert.Nil(t, err) + assert.NotNil(t, capturedRequest, "HTTP request should have been made") + // The request should have been made (we can't easily verify multipart content without parsing it) + mockClient.AssertExpectations(t) +} + +// Test Bug #2: Filename extraction from absolute Windows-style path +func TestUploadFileNexus3_WindowsPath(t *testing.T) { + mockClient := new(MockHttpClient) + mockResp := &http.Response{ + StatusCode: 200, + Body: ioutil.NopCloser(strings.NewReader("Success")), + } + mockClient.On("Do", mock.AnythingOfType("*http.Request")).Return(mockResp, nil) + + tmpFile, err := createTempFile("test content") + assert.NoError(t, err) + defer os.Remove(tmpFile) + + plugin := NexusPlugin{ + PluginProcessingInfo: PluginProcessingInfo{ + UserName: "testUser", + Password: "testPass", + ServerUrl: "https://nexus.example.com", + Repository: "repo", + Format: "maven2", + Version: "nexus3", + }, + HttpClient: mockClient, + } + + // Simulate Windows-style path in artifact (actual file is tmpFile) + artifact := Artifact{ + File: tmpFile, + ArtifactId: "test-app", + Type: "jar", + Version: "1.0", + GroupId: "com.test", + } + + err = plugin.uploadFileNexus3(artifact, tmpFile) + + assert.Nil(t, err) + mockClient.AssertExpectations(t) +} + +// Test Bug #2: Relative path should also work +func TestUploadFileNexus3_RelativePath(t *testing.T) { + mockClient := new(MockHttpClient) + mockResp := &http.Response{ + StatusCode: 200, + Body: ioutil.NopCloser(strings.NewReader("Success")), + } + mockClient.On("Do", mock.AnythingOfType("*http.Request")).Return(mockResp, nil) + + tmpFile, err := createTempFile("test content") + assert.NoError(t, err) + defer os.Remove(tmpFile) + + plugin := NexusPlugin{ + PluginProcessingInfo: PluginProcessingInfo{ + UserName: "testUser", + Password: "testPass", + ServerUrl: "https://nexus.example.com", + Repository: "repo", + Format: "maven2", + Version: "nexus3", + }, + HttpClient: mockClient, + } + + artifact := Artifact{ + File: tmpFile, + ArtifactId: "test-app", + Type: "jar", + Version: "1.0", + GroupId: "com.test", + } + + err = plugin.uploadFileNexus3(artifact, tmpFile) + + assert.Nil(t, err) + mockClient.AssertExpectations(t) +} + +// Test Bug #1: Response Body Reading - 401 Error with Details +func TestUploadFileNexus3_ResponseBody_401(t *testing.T) { + mockClient := new(MockHttpClient) + fakeErrorBody := `{"errors":[{"id":"*","message":"Invalid credentials"}]}` + mockResp := &http.Response{ + StatusCode: 401, + Body: ioutil.NopCloser(strings.NewReader(fakeErrorBody)), + } + mockClient.On("Do", mock.AnythingOfType("*http.Request")).Return(mockResp, nil) + + tmpFile, err := createTempFile("test content") + assert.NoError(t, err) + defer os.Remove(tmpFile) + + plugin := NexusPlugin{ + PluginProcessingInfo: PluginProcessingInfo{ + UserName: "testUser", + Password: "testPass", + ServerUrl: "https://nexus.example.com", + Repository: "repo", + Format: "maven2", + Version: "nexus3", + }, + HttpClient: mockClient, + } + + artifact := Artifact{ + File: tmpFile, + ArtifactId: "test-app", + Type: "jar", + Version: "1.0", + GroupId: "com.test", + } + + err = plugin.uploadFileNexus3(artifact, tmpFile) + + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "Invalid credentials", "Error should include response body details") + mockClient.AssertExpectations(t) +} + +// Test Bug #1: Response Body Reading - 500 Error with Details +func TestUploadFileNexus3_ResponseBody_500(t *testing.T) { + mockClient := new(MockHttpClient) + fakeErrorBody := `{"errors":[{"id":"*","message":"Internal server error: Invalid field value"}]}` + mockResp := &http.Response{ + StatusCode: 500, + Body: ioutil.NopCloser(strings.NewReader(fakeErrorBody)), + } + mockClient.On("Do", mock.AnythingOfType("*http.Request")).Return(mockResp, nil) + + tmpFile, err := createTempFile("test content") + assert.NoError(t, err) + defer os.Remove(tmpFile) + + plugin := NexusPlugin{ + PluginProcessingInfo: PluginProcessingInfo{ + UserName: "testUser", + Password: "testPass", + ServerUrl: "https://nexus.example.com", + Repository: "repo", + Format: "maven2", + Version: "nexus3", + }, + HttpClient: mockClient, + } + + artifact := Artifact{ + File: tmpFile, + ArtifactId: "test-app", + Type: "jar", + Version: "1.0", + GroupId: "com.test", + } + + err = plugin.uploadFileNexus3(artifact, tmpFile) + + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "Internal server error", "Error should include response body details") + mockClient.AssertExpectations(t) +} + +// Test Bug #1: Response Body Reading - 404 Error with Details +func TestUploadFileNexus3_ResponseBody_404(t *testing.T) { + mockClient := new(MockHttpClient) + fakeErrorBody := `{"errors":[{"id":"*","message":"Repository not found"}]}` + mockResp := &http.Response{ + StatusCode: 404, + Body: ioutil.NopCloser(strings.NewReader(fakeErrorBody)), + } + mockClient.On("Do", mock.AnythingOfType("*http.Request")).Return(mockResp, nil) + + tmpFile, err := createTempFile("test content") + assert.NoError(t, err) + defer os.Remove(tmpFile) + + plugin := NexusPlugin{ + PluginProcessingInfo: PluginProcessingInfo{ + UserName: "testUser", + Password: "testPass", + ServerUrl: "https://nexus.example.com", + Repository: "repo", + Format: "maven2", + Version: "nexus3", + }, + HttpClient: mockClient, + } + + artifact := Artifact{ + File: tmpFile, + ArtifactId: "test-app", + Type: "jar", + Version: "1.0", + GroupId: "com.test", + } + + err = plugin.uploadFileNexus3(artifact, tmpFile) + + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "Repository not found", "Error should include response body details") + mockClient.AssertExpectations(t) +} + +// Test Bug #1: Response Body Reading - Success Response with Body +func TestUploadFileNexus3_ResponseBody_Success(t *testing.T) { + mockClient := new(MockHttpClient) + fakeSuccessBody := `{"success":true}` + mockResp := &http.Response{ + StatusCode: 200, + Body: ioutil.NopCloser(strings.NewReader(fakeSuccessBody)), + } + mockClient.On("Do", mock.AnythingOfType("*http.Request")).Return(mockResp, nil) + + tmpFile, err := createTempFile("test content") + assert.NoError(t, err) + defer os.Remove(tmpFile) + + plugin := NexusPlugin{ + PluginProcessingInfo: PluginProcessingInfo{ + UserName: "testUser", + Password: "testPass", + ServerUrl: "https://nexus.example.com", + Repository: "repo", + Format: "maven2", + Version: "nexus3", + }, + HttpClient: mockClient, + } + + artifact := Artifact{ + File: tmpFile, + ArtifactId: "test-app", + Type: "jar", + Version: "1.0", + GroupId: "com.test", + } + + err = plugin.uploadFileNexus3(artifact, tmpFile) + + assert.Nil(t, err, "Should succeed without error") + mockClient.AssertExpectations(t) +}