From 41883d8d289531fbf272a5759be20caa325d4771 Mon Sep 17 00:00:00 2001 From: YutoKashiwagi Date: Wed, 19 Feb 2025 13:59:31 +0900 Subject: [PATCH 1/2] make completeUploadExternal public --- files.go | 49 +++++++++-------- files_test.go | 150 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 175 insertions(+), 24 deletions(-) diff --git a/files.go b/files.go index 0682fb675..3b9bc79c0 100644 --- a/files.go +++ b/files.go @@ -199,14 +199,14 @@ type FileSummary struct { Title string `json:"title"` } -type completeUploadExternalParameters struct { - title string - channel string - initialComment string - threadTimestamp string +type CompleteUploadExternalParameters struct { + Files []FileSummary + Channel string + InitialComment string + ThreadTimestamp string } -type completeUploadExternalResponse struct { +type CompleteUploadExternalResponse struct { SlackResponse Files []FileSummary `json:"files"` } @@ -542,28 +542,28 @@ func (api *Client) uploadToURL(ctx context.Context, params uploadToURLParameters return err } -// completeUploadExternal once files are uploaded, this completes the upload and shares it to the specified channel -func (api *Client) completeUploadExternal(ctx context.Context, fileID string, params completeUploadExternalParameters) (file *completeUploadExternalResponse, err error) { - request := []FileSummary{{ID: fileID, Title: params.title}} - requestBytes, err := json.Marshal(request) +// CompleteUploadExternalContext once files are uploaded, this completes the upload and shares it to the specified channel +// Slack API docs: https://api.slack.com/methods/files.completeUploadExternal +func (api *Client) CompleteUploadExternalContext(ctx context.Context, params CompleteUploadExternalParameters) (file *CompleteUploadExternalResponse, err error) { + filesBytes, err := json.Marshal(params.Files) if err != nil { return nil, err } values := url.Values{ "token": {api.token}, - "files": {string(requestBytes)}, + "files": {string(filesBytes)}, } - if params.channel != "" { - values.Add("channel_id", params.channel) + if params.Channel != "" { + values.Add("channel_id", params.Channel) } - if params.initialComment != "" { - values.Add("initial_comment", params.initialComment) + if params.InitialComment != "" { + values.Add("initial_comment", params.InitialComment) } - if params.threadTimestamp != "" { - values.Add("thread_ts", params.threadTimestamp) + if params.ThreadTimestamp != "" { + values.Add("thread_ts", params.ThreadTimestamp) } - response := &completeUploadExternalResponse{} + response := &CompleteUploadExternalResponse{} err = api.postMethod(ctx, "files.completeUploadExternal", values, response) if err != nil { return nil, err @@ -615,11 +615,14 @@ func (api *Client) UploadFileV2Context(ctx context.Context, params UploadFileV2P return nil, err } - c, err := api.completeUploadExternal(ctx, u.FileID, completeUploadExternalParameters{ - title: params.Title, - channel: params.Channel, - initialComment: params.InitialComment, - threadTimestamp: params.ThreadTimestamp, + c, err := api.CompleteUploadExternalContext(ctx, CompleteUploadExternalParameters{ + Files: []FileSummary{{ + ID: u.FileID, + Title: params.Title, + }}, + Channel: params.Channel, + InitialComment: params.InitialComment, + ThreadTimestamp: params.ThreadTimestamp, }) if err != nil { return nil, err diff --git a/files_test.go b/files_test.go index 1df46aa6e..d86b61fdd 100644 --- a/files_test.go +++ b/files_test.go @@ -2,7 +2,9 @@ package slack import ( "bytes" + "context" "encoding/json" + "fmt" "io" "log" "net/http" @@ -228,7 +230,7 @@ func urlFileUploadHandler(rw http.ResponseWriter, r *http.Request) { func completeURLUpload(rw http.ResponseWriter, r *http.Request) { rw.Header().Set("Content-Type", "application/json") - response, _ := json.Marshal(completeUploadExternalResponse{ + response, _ := json.Marshal(CompleteUploadExternalResponse{ Files: []FileSummary{ { ID: "RandomID", @@ -282,3 +284,149 @@ func TestUploadFileV2(t *testing.T) { t.Errorf("Unexpected error: %s", err) } } + +type mockCompleteUploadExternalHttpClient struct { + ResponseStatus int + ResponseBody []byte +} + +func (m *mockCompleteUploadExternalHttpClient) Do(req *http.Request) (*http.Response, error) { + if req.URL.Path != "files.completeUploadExternal" { + return nil, fmt.Errorf("invalid path: %s", req.URL.Path) + } + + return &http.Response{ + StatusCode: m.ResponseStatus, + Body: io.NopCloser(bytes.NewBuffer(m.ResponseBody)), + }, nil +} + +func TestCompleteUploadExternalContext(t *testing.T) { + type testCase struct { + title string + params CompleteUploadExternalParameters + wantResponse CompleteUploadExternalResponse + wantErr bool + } + testCases := []testCase{ + { + title: "Testing with required parameters", + params: CompleteUploadExternalParameters{ + Files: []FileSummary{ + { + ID: "ID1", + }, + { + ID: "ID2", + }, + }, + }, + wantResponse: CompleteUploadExternalResponse{ + Files: []FileSummary{ + { + ID: "ID1", + }, + { + ID: "ID2", + }, + }, + SlackResponse: SlackResponse{Ok: true}, + }, + }, + { + title: "Testing with optional parameters", + params: CompleteUploadExternalParameters{ + Files: []FileSummary{ + { + ID: "ID1", + }, + { + ID: "ID2", + Title: "Title2", + }, + }, + Channel: "test-channel", + InitialComment: "test-comment", + ThreadTimestamp: "1234567890.123456", + }, + wantResponse: CompleteUploadExternalResponse{ + Files: []FileSummary{ + { + ID: "ID1", + }, + { + ID: "ID2", + Title: "Title2", + }, + }, + SlackResponse: SlackResponse{Ok: true}, + }, + }, + { + title: "Testing with error", + params: CompleteUploadExternalParameters{ + Files: []FileSummary{ + { + ID: "ID1", + }, + }, + }, + wantErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + var resBody map[string]interface{} + if !tc.wantErr { + resBody = map[string]interface{}{ + "ok": true, + } + files := make([]map[string]string, 0) + for _, file := range tc.params.Files { + m := map[string]string{ + "id": file.ID, + } + if file.Title != "" { + m["title"] = file.Title + } + files = append(files, m) + } + resBody["files"] = files + } else { + resBody = map[string]interface{}{ + "ok": false, + "error": "errored", + } + } + + resBodyBytes, err := json.Marshal(resBody) + if err != nil { + t.Fatalf("failed to marshal response body: %v", err) + } + + api := &Client{ + token: validToken, + httpclient: &mockCompleteUploadExternalHttpClient{ + ResponseStatus: 200, + ResponseBody: resBodyBytes, + }, + } + + gotResponse, err := api.CompleteUploadExternalContext(context.Background(), tc.params) + + if err != nil { + if !tc.wantErr { + t.Errorf("CompleteUploadExternalContext() error = %v, want nil", err) + } + } else { + if tc.wantErr { + t.Fatalf("CompleteUploadExternalContext() error = nil, want %v", tc.wantErr) + } + if !reflect.DeepEqual(gotResponse, &tc.wantResponse) { + t.Errorf("CompleteUploadExternalContext() = %v, want %v", gotResponse, tc.wantResponse) + } + } + }) + } +} From 53a00c2148a246ea6fd67ef3c7d0c42b9b885409 Mon Sep 17 00:00:00 2001 From: YutoKashiwagi Date: Wed, 19 Feb 2025 15:50:00 +0900 Subject: [PATCH 2/2] make getUploadURLExternal public --- files.go | 48 ++++++++++++--------- files_test.go | 117 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 144 insertions(+), 21 deletions(-) diff --git a/files.go b/files.go index 3b9bc79c0..c1471605f 100644 --- a/files.go +++ b/files.go @@ -173,14 +173,14 @@ type UploadFileV2Parameters struct { SnippetText string } -type getUploadURLExternalParameters struct { - altText string - fileSize int - fileName string - snippetText string +type GetUploadURLExternalParameters struct { + AltText string + FileSize int + FileName string + SnippetText string } -type getUploadURLExternalResponse struct { +type GetUploadURLExternalResponse struct { UploadURL string `json:"upload_url"` FileID string `json:"file_id"` SlackResponse @@ -506,20 +506,28 @@ func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string) return &response.File, response.Comments, &response.Paging, nil } -// getUploadURLExternal gets a URL and fileID from slack which can later be used to upload a file. -func (api *Client) getUploadURLExternal(ctx context.Context, params getUploadURLExternalParameters) (*getUploadURLExternalResponse, error) { +// GetUploadURLExternalContext gets a URL and fileID from slack which can later be used to upload a file. +// Slack API docs: https://api.slack.com/methods/files.getUploadURLExternal +func (api *Client) GetUploadURLExternalContext(ctx context.Context, params GetUploadURLExternalParameters) (*GetUploadURLExternalResponse, error) { + if params.FileName == "" { + return nil, fmt.Errorf("GetUploadURLExternalContext: fileName cannot be empty") + } + if params.FileSize == 0 { + return nil, fmt.Errorf("GetUploadURLExternalContext: fileSize cannot be 0") + } + values := url.Values{ "token": {api.token}, - "filename": {params.fileName}, - "length": {strconv.Itoa(params.fileSize)}, + "filename": {params.FileName}, + "length": {strconv.Itoa(params.FileSize)}, } - if params.altText != "" { - values.Add("initial_comment", params.altText) + if params.AltText != "" { + values.Add("initial_comment", params.AltText) } - if params.snippetText != "" { - values.Add("thread_ts", params.snippetText) + if params.SnippetText != "" { + values.Add("thread_ts", params.SnippetText) } - response := &getUploadURLExternalResponse{} + response := &GetUploadURLExternalResponse{} err := api.postMethod(ctx, "files.getUploadURLExternal", values, response) if err != nil { return nil, err @@ -594,11 +602,11 @@ func (api *Client) UploadFileV2Context(ctx context.Context, params UploadFileV2P return nil, fmt.Errorf("file.upload.v2: file size cannot be 0") } - u, err := api.getUploadURLExternal(ctx, getUploadURLExternalParameters{ - altText: params.AltTxt, - fileName: params.Filename, - fileSize: params.FileSize, - snippetText: params.SnippetText, + u, err := api.GetUploadURLExternalContext(ctx, GetUploadURLExternalParameters{ + AltText: params.AltTxt, + FileName: params.Filename, + FileSize: params.FileSize, + SnippetText: params.SnippetText, }) if err != nil { return nil, err diff --git a/files_test.go b/files_test.go index d86b61fdd..de98cf352 100644 --- a/files_test.go +++ b/files_test.go @@ -216,7 +216,7 @@ func TestUploadFileWithoutFilename(t *testing.T) { func uploadURLHandler(rw http.ResponseWriter, r *http.Request) { rw.Header().Set("Content-Type", "application/json") - response, _ := json.Marshal(getUploadURLExternalResponse{ + response, _ := json.Marshal(GetUploadURLExternalResponse{ FileID: "RandomID", UploadURL: "http://" + serverAddr + "/abc", SlackResponse: SlackResponse{Ok: true}}) @@ -285,6 +285,121 @@ func TestUploadFileV2(t *testing.T) { } } +type mockGetUploadURLExternalHttpClient struct { + ResponseStatus int + ResponseBody []byte +} + +func (m *mockGetUploadURLExternalHttpClient) Do(req *http.Request) (*http.Response, error) { + if req.URL.Path != "files.getUploadURLExternal" { + return nil, fmt.Errorf("invalid path: %s", req.URL.Path) + } + + return &http.Response{ + StatusCode: m.ResponseStatus, + Body: io.NopCloser(bytes.NewBuffer(m.ResponseBody)), + }, nil +} + +func TestGetUploadURLExternalContext(t *testing.T) { + type testCase struct { + title string + params GetUploadURLExternalParameters + wantSlackResponse []byte + wantResponse GetUploadURLExternalResponse + wantErr error + } + testCases := []testCase{ + { + title: "Testing with required parameters", + params: GetUploadURLExternalParameters{ + FileName: "test.txt", + FileSize: 10, + }, + wantSlackResponse: []byte(`{"ok":true,"file_id":"RandomID","upload_url":"http://test-server/abc"}`), + wantResponse: GetUploadURLExternalResponse{ + FileID: "RandomID", + UploadURL: "http://test-server/abc", + SlackResponse: SlackResponse{ + Ok: true, + }, + }, + }, + { + title: "Testing with optional parameters", + params: GetUploadURLExternalParameters{ + FileSize: 10, + FileName: "test.txt", + AltText: "test-alt-text", + SnippetText: "test-snippet-text", + }, + wantSlackResponse: []byte(`{"ok":true,"file_id":"RandomID","upload_url":"http://test-server/abc"}`), + wantResponse: GetUploadURLExternalResponse{ + FileID: "RandomID", + UploadURL: "http://test-server/abc", + SlackResponse: SlackResponse{ + Ok: true, + }, + }, + }, + { + title: "Testing with request error", + params: GetUploadURLExternalParameters{ + FileName: "test.txt", + FileSize: 10, + }, + wantSlackResponse: []byte(`{"ok":false,"error":"errored"}`), + wantErr: fmt.Errorf("errored"), + }, + { + title: "Testing with invalid parameters: empty file name", + params: GetUploadURLExternalParameters{ + FileName: "", + FileSize: 10, + }, + wantErr: fmt.Errorf("GetUploadURLExternalContext: fileName cannot be empty"), + }, + { + title: "Testing with invalid parameters: file size 0", + params: GetUploadURLExternalParameters{ + FileName: "test.txt", + FileSize: 0, + }, + wantErr: fmt.Errorf("GetUploadURLExternalContext: fileSize cannot be 0"), + }, + } + + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + api := &Client{ + token: validToken, + httpclient: &mockGetUploadURLExternalHttpClient{ + ResponseStatus: 200, + ResponseBody: tc.wantSlackResponse, + }, + } + + gotResponse, err := api.GetUploadURLExternalContext(context.Background(), tc.params) + + if err != nil { + if tc.wantErr == nil { + t.Fatalf("GetUploadURLExternalContext() error = %v, want nil", err) + } + if err.Error() != tc.wantErr.Error() { + t.Errorf("GetUploadURLExternalContext() error = %v, want %v", err, tc.wantErr) + } + } else { + if tc.wantErr != nil { + t.Fatalf("GetUploadURLExternalContext() error = nil, want %v", tc.wantErr) + } + if !reflect.DeepEqual(gotResponse, &tc.wantResponse) { + t.Errorf("GetUploadURLExternalContext() = %v, want %v", gotResponse, tc.wantResponse) + } + } + }) + } +} + type mockCompleteUploadExternalHttpClient struct { ResponseStatus int ResponseBody []byte