2024-05-27 (C) Questetra, Inc. (MIT License) 3 2 This item uploads files to Slack with Bots. この工程は、Bots 機能を使って Slack にファイルをアップロードします。 https://support.questetra.com/bpmn-icons/service-task-slack-file-upload-bots-v2 https://support.questetra.com/ja/bpmn-icons/service-task-slack-file-upload-bots-v2 iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABFBJREFUWEfN l39ME2cYx79vofwSEljXirCwLWOSYJRotjgnJDPZWNKjg4BYpDgnCCuYKCxmxLUu7pcJZupitmqY XTfFLRnaqeXqYibbXExIdNQ/tj+WLMvCEjPoGGABxULf5b3Sy7W9a0+zhd0f/aP3Ps/zeZ/fR7DE D1Fr31RgygrNh2pBsYmCrAXoYwDyFuUnAPI7AfWB4FtNqsbtueWZVaM7KYBRb8wnIN0UsALIUKMU wF0CnKCgPV6/989EMgkBjIYqKyh9H8AylYZjj82AkL3esYETSvKKANzDnIMStD+g4SgxQnGc/4vv kNMlC2A0cJ+DYuu/YVzUQfCFd4xvjNUZB6Dm5kUlRRj5ZUTUlZOXA41Gg6nxqYTMcp6IAliM+XEl LcyQa9iFzGUZmJ6cxrbVL6PhNTO2dG4BIQS8i4ejW1E8rJaQdmlOiAAs2wHya6KEaznQjNqOWpHv 1MFTgvGMrHBxzAfnUV1YkyxyMwAtjlSHCMDpuaMU6EwkXVZRhoPn3hOPeD+7hBctlUhJTRH/4wxV yQBAgA94P98lOIT9sCazEAyNq6lzBtHR0460jDSc+9CNtndb7xuA9YkUrUbHmpUAwBm4JkpxOha9 4PEVaHmrBfmP5ke9unc3CM9JDwb7B3Hx1oU4gFXrS9HU3YShr4dwofeirEcIwTZ+jO8LA+g5JwWa Y0/2/XQaeYZIt43Xs+eFThy5dDgKwLyyAWd+7kOqNlUQ6GnrwdXzP8QJE+AT3s+3CABGfdUwQNfG nhoY9QjZrfSwJDR3mZGemS4cCd4Lwv2RW/gv8jAvHd51REYF8Xn9A+sWAbi/JYNFPGz71IZnjRtk 7c/dmYOl1ILttu0w7TQJZxyvO6B/RI/63fViVbRteBWjI6NyOia8fv6hCABVumVxWTHWbFyNhfkF TI3fDt90LohrA9dEEZYrLFSjf4xBl6/DqmdKMTt9B5fPXEZoIaToQa+fD/vXqOdkAXbsfwV1u+pA NERwr/nJBnw81AvdCp1wq53rW4XbN7+5Q4y51NqkfxLW8nYEJgKyEFIA2RBIM1wuvr32XjTubUR2 brbiLRN0R2kI5JPw7G/9yMzOFJT7vvdh5vYsyk0bRWOsEZWVr0HhE4WKAM4DTrgdXyVOQqUyfPr5 p9D6TisCkwHYN9vReawrDsD1tgs21xsoWVcSZYTlzPB3PhyyHgINxUc4qgyVGlEs9j7nvjiAXH0u snKy8OOVGwo3lXdOVCNS24rlAKSz4ObVm7BttiuGQ/IiuhWzF2qG0Z6ju1FpqRT19B/rR421Bto0 rfAfc/tLBdVJAeKGUbgUk4/j5UXL4bx+UuiOLK6NpRZhMFVUV4iJaq/fnwxAfhwLEOElNOFGwUpu U91zuPLlIGYD4c2bTci0dC2uf3MjmXHlhSQiqWYlS25FIfFkltP/31L6X3jivtfyCMSSfpiIEEv5 aSZNpSX7OH3QjFcr9w829dcwn81r2gAAAABJRU5ErkJggg== { const auth = httpClient.createAuthSettingOAuth2( 'Slack', 'https://slack.com/oauth/v2/authorize', 'https://slack.com/api/oauth.v2.access', 'files:write', 'consumer_key', 'consumer_secret', 'access_token' ); configs.putObject('conf_OAuth2', auth); configs.put('conf_ChannelId', channelId); const fileDef = engine.createDataDefinition('ファイル', 1, 'q_files', 'FILE'); configs.putObject('conf_Files', fileDef); engine.setData(fileDef, files); }; /** * 指定サイズのテキストファイルを作成 * @param name * @param size */ const createQfile = (name, size) => { let text = ''; while (text.length < size) { if (text.length !== 0 && text.length * 2 < size) { text += text; } else { text += 'a'; } } return engine.createQfile(name, 'text/plain; charset=UTF-8', text); }; /** * 異常系のテスト * @param errorMsg */ const assertError = (errorMsg) => { let failed = false; try { main(); } catch (e) { failed = true; expect(e.message).toEqual(errorMsg); } if (!failed) { fail('No error was thrown.'); } }; /** * ファイルが添付されていない場合(正常終了) */ test('No File to upload', () => { prepareConfigs('channel1', null); expect(main()).toEqual(undefined); }); /** * ファイル数が HTTP リクエスト数制限を超えてエラーになる場合 */ test('The number of files exceeds the limit', () => { const files = new java.util.ArrayList(); for (let i = 0; i < httpClient.getRequestingLimit() / 2; i++) { files.add(createQfile(`ファイル${i}.txt`, 1)); } prepareConfigs('channel1', files); assertError('The number of files exceeds the limit.'); }); /** * クエリパラメータのテスト用の文字列を生成する * @param key * @param value * @returns {String} */ const generateQueryString = (key, value) => { const encodedKey = encodeURIComponent(key); const encodedValue = encodeURIComponent(value) .replace(/%20/g, '+') // HttpRequestWrapper#formParam() はスペースを + に置き換える .replace(/'/g, '%27') // encodeURIComponent() でエンコードされない文字をエンコード .replace(/\(/g, '%28') .replace(/\)/g, '%29'); return `${encodedKey}=${encodedValue}`; }; /** * アップロード URL 取得の GET リクエストのテスト * @param {Object} request * @param request.url * @param request.method * @param request.headers * @param file */ const assertGetUploadUrlRequest = ({url, method, headers}, file) => { const expectedUrl = 'https://slack.com/api/files.getUploadURLExternal' + `?${generateQueryString('filename', file.getName())}` + `&${generateQueryString('length', file.getLength().toString())}`; expect(url).toEqual(expectedUrl); expect(method).toEqual('GET'); expect(headers.Authorization).toEqual('Bearer access_token'); }; /** * アップロード URL 取得の GET リクエストでエラーになる場合 * レスポンスが JSON でない */ test('Get Upload URL Request Failed - failed to parse', () => { const files = new java.util.ArrayList(); files.add(createQfile('テキストファイル.txt', 1)); prepareConfigs('channel2', files); httpClient.setRequestHandler((request) => { assertGetUploadUrlRequest(request, files.get(0)); return httpClient.createHttpResponse(400, 'text/plain', 'error'); }); assertError('Failed to get upload URL. status: 400'); }); /** * エラーレスポンスを作成 * @param status * @param errorMsg */ const createErrorResponse = (status, errorMsg) => { return httpClient.createHttpResponse( status, 'application/json', JSON.stringify({ ok: false, error: errorMsg }) ); }; /** * アップロード URL 取得の GET リクエストでエラーになる場合 * レスポンスが ok でない */ test('Get Upload URL Request Failed - result is not ok', () => { const files = new java.util.ArrayList(); files.add(createQfile('サイズが0のファイル.txt', 0)); prepareConfigs('channel2', files); httpClient.setRequestHandler((request) => { assertGetUploadUrlRequest(request, files.get(0)); return createErrorResponse(200, 'missing_argument'); }); assertError('Failed to get upload URL\n filename: サイズが0のファイル.txt error: missing_argument'); }); /** * アップロード URL 取得の成功レスポンスを作成 * @param status * @param errorMsg */ const createUploadUrlResponse = (uploadUrl, fileId) => { return httpClient.createHttpResponse( 200, 'application/json', JSON.stringify({ ok: true, upload_url: uploadUrl, file_id: fileId }) ); }; /** * ファイルアップロードの POST リクエストのテスト * @param {Object} request * @param request.url * @param request.method * @param request.contentType * @param request.body * @param uploadUrl * @param uploadFile */ const assertUploadRequest = ({url, method, contentType, body}, uploadUrl, uploadFile) => { expect(url).toEqual(uploadUrl); expect(method).toEqual('POST'); expect(contentType).startsWith('multipart/form-data'); expect(body.size()).toEqual(1); const file = body.get('file').get(0); expect(file.getName()).toEqual(uploadFile.getName()); expect(file.getContentType()).toEqual(uploadFile.getContentType()); expect(file.getLength()).toEqual(uploadFile.getLength()); }; /** * ファイルアップロードの POST リクエストでエラーになる場合 * レスポンスのステータスが 200 でない */ test('File Upload Request Failed - status is not 200', () => { const files = new java.util.ArrayList(); files.add(createQfile('テキストファイル.txt', 1)); prepareConfigs('channel1', files); let reqCount = 0; const uploadUrl = 'https://example.com/upload1'; const fileId = 'file_id_1'; httpClient.setRequestHandler((request) => { if (reqCount === 0) { assertGetUploadUrlRequest(request, files.get(0)); reqCount++; return createUploadUrlResponse(uploadUrl, fileId); } assertUploadRequest(request, uploadUrl, files.get(0)); return httpClient.createHttpResponse(400, 'text/plain', ''); }); assertError('Failed to upload. status: 400 filename: テキストファイル.txt'); }); /** * アップロード完了の POST リクエストのテスト * @param {Object} request * @param request.url * @param request.method * @param request.headers * @param request.body * @param fileIds * @param channelId */ const assertCompleteRequest = ({url, method, headers, body}, fileIds, channelId) => { expect(url).toEqual('https://slack.com/api/files.completeUploadExternal'); expect(method).toEqual('POST'); expect(headers.Authorization).toEqual('Bearer access_token'); const json = JSON.parse(body); expect(json.channel_id).toEqual(channelId); fileIds.forEach((fileId, i) => { expect(json.files[i].id).toEqual(fileId); }); }; /** * アップロード完了の POST リクエストでエラーになる場合 * レスポンスが JSON でない */ test('Complete Request Failed - failed to parse', () => { const files = new java.util.ArrayList(); files.add(createQfile('テキストファイル.txt', 1)); prepareConfigs('channel1', files); let reqCount = 0; const uploadUrl = 'https://example.com/upload1'; const fileId = 'file_id_1'; httpClient.setRequestHandler((request) => { if (reqCount === 0) { assertGetUploadUrlRequest(request, files.get(0)); reqCount++; return createUploadUrlResponse(uploadUrl, fileId); } if (reqCount === 1) { assertUploadRequest(request, uploadUrl, files.get(0)); reqCount++; return httpClient.createHttpResponse(200, 'text/plain', ''); } assertCompleteRequest(request, [fileId], 'channel1'); return httpClient.createHttpResponse(400, 'text/plain', 'error'); }); assertError('Failed to complete upload. status: 400'); }); /** * アップロード完了の POST リクエストでエラーになる場合 * レスポンスが ok でない */ test('Complete Request Failed - result is not ok', () => { const files = new java.util.ArrayList(); files.add(createQfile('テキストファイル.txt', 1)); prepareConfigs('channel1', files); let reqCount = 0; const uploadUrl = 'https://example.com/upload1'; const fileId = 'file_id_1'; httpClient.setRequestHandler((request) => { if (reqCount === 0) { assertGetUploadUrlRequest(request, files.get(0)); reqCount++; return createUploadUrlResponse(uploadUrl, fileId); } if (reqCount === 1) { assertUploadRequest(request, uploadUrl, files.get(0)); reqCount++; return httpClient.createHttpResponse(200, 'text/plain', ''); } assertCompleteRequest(request, [fileId], 'channel1'); return createErrorResponse(200, 'access_denied'); }); assertError('Failed to complete upload\n error: access_denied'); }); /** * 成功 - ファイルが 1 つの場合 */ test('Succeed - upload one file', () => { const files = new java.util.ArrayList(); files.add(createQfile('テキストファイル.txt', 1)); prepareConfigs('channel1', files); let reqCount = 0; const uploadUrl = 'https://example.com/upload1'; const fileId = 'file_id_1'; httpClient.setRequestHandler((request) => { if (reqCount === 0) { assertGetUploadUrlRequest(request, files.get(0)); reqCount++; return createUploadUrlResponse(uploadUrl, fileId); } if (reqCount === 1) { assertUploadRequest(request, uploadUrl, files.get(0)); reqCount++; return httpClient.createHttpResponse(200, 'text/plain', ''); } assertCompleteRequest(request, [fileId], 'channel1'); return httpClient.createHttpResponse(200, 'application/json', '{"ok":true}'); }); expect(main()).toEqual(undefined); }); /** * 成功 - ファイルを上限個数アップロード */ test('Succeed - upload maximum number of files', () => { const files = new java.util.ArrayList(); const fileMaxNum = httpClient.getRequestingLimit() / 2 - 1; for (let i = 0; i < fileMaxNum; i++) { files.add(createQfile(`ファイル${i+1}.txt`, 1)); } prepareConfigs('channel2', files); let reqCount = 0; let fileIndex = 0; const uploadUrls = []; const fileIds = []; for (let i = 0; i < fileMaxNum; i++) { uploadUrls.push(`https://example.com/upload${i+1}`); fileIds.push(`file_id_${i+1}`); } httpClient.setRequestHandler((request) => { if (reqCount % 2 === 0 && reqCount < files.size() * 2) { assertGetUploadUrlRequest(request, files.get(fileIndex)); reqCount++; return createUploadUrlResponse(uploadUrls[fileIndex], fileIds[fileIndex]); } if (reqCount % 2 === 1) { assertUploadRequest(request, uploadUrls[fileIndex], files.get(fileIndex)); reqCount++; fileIndex++; return httpClient.createHttpResponse(200, 'text/plain', ''); } assertCompleteRequest(request, fileIds, 'channel2'); return httpClient.createHttpResponse(200, 'application/json', '{"ok":true}'); }); expect(main()).toEqual(undefined); expect(reqCount).toEqual(files.size() * 2); // complete のリクエストではカウントアップしていない expect(fileIndex).toEqual(files.size()); }); ]]>