3
2
2024-11-21
(C) Questetra, Inc. (MIT License)
This item downloads the specified Google Workspace files (such as Google Docs, Sheets, or Slides) in PDF format.
You can download multiple files at once.
When you download multiple files, you should write one File ID per line.
この工程は、指定した Google Workspace ファイル(Google ドキュメント、スプレッドシート、スライド等)を PDF
形式でダウンロードします。一度に複数のダウンロードが可能です。複数ダウンロードする場合、データ項目では
1 行につき 1 つずつファイル ID を書くようにしてください。
https://support.questetra.com/bpmn-icons/service-task-google-drive-file-export/
https://support.questetra.com/ja/bpmn-icons/service-task-google-drive-file-export/
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAA71JREFUWEfF
V11oFFcU/s7M7K6TRs1vK1VUSGmbCK2SB6VFgpLWlYC2omIDLaUlCBGfimwWgw9Fu9mW0ocSoUhE
aKmlgUgUyShiEUWSh4AW7L9CS5PGZhuTJmTi7swcmUn3d+ZOdtGy87Ts3HvOd77zfefeIZT5oVLy
h2MP3wTMViJqZqABQM1/+6cIuMvMo4B8RYuGzhUbd0kArT1TK2VWj5CEg2DUFRWYkGALX5ikf3Kl
q2bGb48vgJ0x/T2WEC86cWEmQoIsRIai6mkRCCGAcHy+F0ydRVW81CLik1qk4pDXMk8A4bj+NRhv
LRW3pPeEs1pEbXeTVPDPE63clc3NRB4DTs8JfX6V7d2soKVRgizlk5cyGBdvmbj8velLDDHez9VE
JoqtdoXU3/wE19Io452tCtQgUP2Uu3u/TliInE1CT/pgICQM1p9LuyMTZUdMP06Eo37wYweC2LhO
wsQ0o245QZHzVxsm8O2IgS+vG74sMOPEpajabS/KAAjH9Um/6vdvUdD+qoKQAiykgFmdUb/CzcLE
DCN+PoWfxi0xCEJCi6j1GQDOhCNrQLRjdQ2h+40g1tdnE/4zy6gIkdOO3Mdi4OodE59eTPmbhKU9
9sR0IoZj870gsec7tivY1axAkbIxDQuY/JexaiWBCoiYnmd8fsnAzV98BMl8UotWHHK27uzRhxnY
7AX55XUSjrQFULvcTbdNs92G2kr3u1u/W4h+I1YjASNDXeqWRQZ69EnAe85Hdwew9QXZVeXsAqP3
soENayS0bZJR4EpHJ2eupTA4KmQhoXWpi00N9+j2qhyCF7l4/SUZHdsUVC7Lr9Du87UfTXx8IYW1
dYRje4JYXe1m4d7fjI8GkxibYi9yLa1LlYUAbHEd3xdE0xoXLhQqPdchuZlsnZwfNXDqqqct8wC4
WlBK0FLA5gDMtsBLhJ+9HcSLz7qrH3/AmFtgBJR8ym0uQwG4tGJawNBtE/3D+Szki9DDhrubZbzb
EsCyQBaz3wASmX7sAePDgST+SBToINeGokF0Yn8Qm9ZLTlXMwF/TjKdXuEewKHnSAPpHDHx1w0MD
uYPIcYLHKH7leRmHdyioqiCHdltU9u9inx/+tNDd73E4FY5iO6DoMPqgLYBtTTLuzzBWVZHL7yIw
cw8Zfd8Z0G6754DnYSQ6jm2fd74WQMMzhMpQcdXb7br+s4nYoMd5IDqOnZFcxIWkWPpF64QXkvSG
sl7JsiDKeCn9X5go9VqeBlHWD5M0iLJ+mhUquSwfp49ru6X2PwLQFL0wM02BagAAAABJRU5ErkJg
gg==
{
// 認証設定を作成し、指定
const jwtAuth = httpClient.createAuthSettingOAuth2JwtBearer('JWT', scope, '', '', privateKeyId, privateKey, serviceAccount, '');
configs.putObject('conf_Auth', jwtAuth);
// ファイル ID が保存されているデータ項目を作成し、指定
const fileIdsDef = engine.createDataDefinition('ファイル ID', 1, 'q_fileIds', 'STRING_TEXTAREA');
configs.putObject('conf_FileIds', fileIdsDef);
engine.setData(fileIdsDef, fileIds);
// ファイルを保存するデータ項目を作成し、指定
const fileDef = engine.createDataDefinition('ファイル', 2, 'q_files', 'FILE');
configs.putObject('conf_FileData', fileDef);
engine.setData(fileDef, files);
return fileDef;
};
/**
* 異常系のテスト
* @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('Scope is empty', () => {
const scope = '';
const privateKeyId = 'key-12345';
const serviceAccount = 'service@questetra.com';
const fileIds = '1234567890';
prepareConfigs(scope, privateKeyId, PRIVATE_KEY, serviceAccount, fileIds, []);
assertError(`Scope ${SCOPE} must be included in the scope.`);
});
const ANOTHER_SCOPE = 'https://www.googleapis.com/auth/cloud-platform';
/**
* スコープに必須スコープを含まない
*/
test('Required scope is missing', () => {
const privateKeyId = 'key-12345';
const serviceAccount = 'service@questetra.com';
const fileIds = '1234567890';
prepareConfigs(ANOTHER_SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, fileIds, []);
assertError(`Scope ${SCOPE} must be included in the scope.`);
});
/**
* 秘密鍵 ID が空
*/
test('Private Key ID is empty', () => {
const privateKeyId = '';
const serviceAccount = 'service@questetra.com';
const fileIds = '1234567890';
prepareConfigs(SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, fileIds, []);
assertError('Private Key ID is required.');
});
/**
* カスタム秘密情報1 に設定されているサービスアカウントが空
*/
test('Service Account is empty', () => {
const privateKeyId = 'key-12345';
const serviceAccount = '';
const fileIds = '1234567890';
prepareConfigs(SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, fileIds, []);
assertError('Service Account must be set to Custom Secret 1.');
});
/**
* ファイル ID が空
*/
test('No File IDs - null', () => {
const privateKeyId = 'key-12345';
const serviceAccount = 'service@questetra.com';
prepareConfigs(SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, null, []);
assertError('No File IDs.');
});
/**
* ファイル ID が空行のみ
*/
test('No File IDs - blank line', () => {
const privateKeyId = 'key-12345';
const serviceAccount = 'service@questetra.com';
const fileIds = '\n\n';
prepareConfigs(SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, fileIds, []);
assertError('No File IDs.');
});
/**
* ファイル ID の数が多すぎる
*/
test('Too many File IDs', () => {
const privateKeyId = 'key-12345';
const serviceAccount = 'service@questetra.com';
let fileIds = '';
let httpReqCount = 0;
while (true) {
fileIds += '1234567890\n';
httpReqCount += REQUEST_NUM_PER_FILE;
if (httpReqCount > httpClient.getRequestingLimit()) {
break;
}
}
prepareConfigs(SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, fileIds, []);
assertError('Number of File IDs exceeds the limit.');
});
/**
* トークン取得リクエストのテスト
* @param {Object} request
* @param request.url
* @param request.method
* @param request.contentType
* @param request.body
* @param {String} scope
* @param {String} serviceAccount
*/
const assertTokenRequest = ({
url,
method,
contentType,
body
}, scope, serviceAccount) => {
expect(url).toEqual(URL_TOKEN_REQUEST);
expect(method).toEqual('POST');
expect(contentType).startsWith('application/x-www-form-urlencoded');
const query = `grant_type=${encodeURIComponent('urn:ietf:params:oauth:grant-type:jwt-bearer')}&assertion=`;
expect(body).startsWith(query);
const assertion = decodeURIComponent(body.substring(query.length));
const publicKey = rsa.readKeyFromX509(CERTIFICATE);
expect(jwt.verify(assertion, publicKey)).toEqual(true);
const payloadJson = base64.decodeFromUrlSafeString(assertion.split('.')[1]);
const payload = JSON.parse(payloadJson);
expect(payload.iss).toEqual(serviceAccount);
expect(payload.aud).toEqual(URL_TOKEN_REQUEST);
expect(payload.scope).toEqual(scope);
};
/**
* メタデータ取得の API リクエストのテスト
* @param {Object} request
* @param request.url
* @param request.method
* @param request.contentType
* @param request.headers
* @param request.body
* @param {String} token
* @param {String} fileId
*/
const assertGetMetadataRequest = ({url, method, headers, body}, token, fileId) => {
expect(url).toEqual(`https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}?supportsAllDrives=true&fields=name`);
expect(method).toEqual('GET');
expect(headers.Authorization).toEqual(`Bearer ${token}`);
};
/**
* メタデータ取得の API リクエストでエラー
*/
test('Fail to get metadata', () => {
const privateKeyId = 'key-12345';
const serviceAccount = 'service@questetra.com';
const fileId = '123456789';
const fileDef = prepareConfigs(SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, fileId, []);
const token = 'token';
let reqCount = 0;
httpClient.setRequestHandler((request) => {
if (reqCount++ === 0) {
assertTokenRequest(request, SCOPE, serviceAccount);
return httpClient.createHttpResponse(200, 'application/json', JSON.stringify({
'access_token': token
}));
}
assertGetMetadataRequest(request, token, fileId);
return httpClient.createHttpResponse(400, 'application/json', '{}');
});
assertError('Failed to get metadata of file. status: 400');
});
/**
* ファイルエクスポートの API リクエストのテスト
* @param {Object} request
* @param request.url
* @param request.method
* @param request.headers
* @param request.body
* @param {String} token
* @param {String} fileId
*/
const assertExportFileRequest = ({url, method, headers, body}, token, fileId) => {
expect(url).toEqual(`https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}/export?mimeType=${encodeURIComponent(CONTENT_TYPE)}`);
expect(method).toEqual('GET');
expect(headers.Authorization).toEqual(`Bearer ${token}`);
};
/**
* ファイルエクスポートの API リクエストでエラー
*/
test('Fail to export file', () => {
const privateKeyId = 'key-12345';
const serviceAccount = 'service@questetra.com';
const fileId = '123456789';
const fileDef = prepareConfigs(SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, fileId, []);
const token = 'token';
let reqCount = 0;
httpClient.setRequestHandler((request) => {
if (reqCount % 2 === 0) {
assertTokenRequest(request, SCOPE, serviceAccount);
reqCount++;
return httpClient.createHttpResponse(200, 'application/json', JSON.stringify({
'access_token': token
}));
}
if (reqCount === 1) {
assertGetMetadataRequest(request, token, fileId);
reqCount++;
return httpClient.createHttpResponse(200, 'application/json', JSON.stringify({
'name': 'Google ドキュメント'
}));
}
assertExportFileRequest(request, token, fileId);
return httpClient.createHttpResponse(400, 'application/json', '{}');
});
assertError('Failed to export file. status: 400');
});
/**
* ファイルデータの準備
* @param {String} id
* @param {String} name
* @param {String} body
* @returns {Object}
*/
const prepareFileData = (id, name, body) => {
return {
id,
name,
body
};
};
/**
* ファイルのテスト
* @param file
* @param downloadedFile.name
* @param downloadedFile.body
*/
const assertFile = (file, {name, body}) => {
expect(file.getName()).toEqual(`${name}.pdf`); // 拡張子 .pdf が付く
expect(file.getContentType()).toEqual(CONTENT_TYPE);
let text = '';
fileRepository.readFile(file, 'UTF-8', line => text += line + '\n');
expect(text).toEqual(`${body}\n`);
};
/**
* 成功 - ファイル ID をデータ項目で指定し、事前にファイルが添付されていない場合
*/
test('Succeed - file IDs set by data item, no pre-attached files', () => {
const privateKeyId = 'key-12345';
const serviceAccount = 'service@questetra.com';
const filesToDownload = [
prepareFileData('id-1', 'Google ドキュメント', 'This is the content of a Google Document.'),
prepareFileData('id-2', 'Google スプレッドシート', 'This is the content of a Google Spreadsheet.'),
prepareFileData('id-3', 'Google スライド', 'This is the content of a Google Slide.'),
];
const fileIdsStr = filesToDownload.map(file => file.id).join('\n') + '\n';
const fileDef = prepareConfigs(SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, fileIdsStr, []);
const token = 'token1';
let reqCount = 0;
httpClient.setRequestHandler((request) => {
const fileData = filesToDownload[Math.floor(reqCount / REQUEST_NUM_PER_FILE)];
if (reqCount % REQUEST_NUM_PER_FILE === 0 || reqCount % REQUEST_NUM_PER_FILE === 2) {
assertTokenRequest(request, SCOPE, serviceAccount);
reqCount++;
return httpClient.createHttpResponse(200, 'application/json', JSON.stringify({
'access_token': token
}));
}
if (reqCount % REQUEST_NUM_PER_FILE === 1) { // メタデータ取得のリクエスト
assertGetMetadataRequest(request, token, fileData.id);
reqCount++;
return httpClient.createHttpResponse(200, 'application/json', JSON.stringify({
'name': fileData.name
}));
}
// ファイルエクスポートのリクエスト
assertExportFileRequest(request, token, fileData.id);
reqCount++;
return httpClient.createHttpResponse(200, CONTENT_TYPE, fileData.body);
});
expect(main()).toEqual(undefined);
expect(reqCount).toEqual(filesToDownload.length * REQUEST_NUM_PER_FILE);
// ファイルのテスト
const files = engine.findData(fileDef);
expect(files.size()).toEqual(filesToDownload.length);
filesToDownload.forEach((expectedFile, i) => assertFile(files[i], expectedFile));
});
/**
* 成功 - ファイル ID を固定値で指定し、事前にファイルが添付されている場合
*/
test('Succeed - file ID set as fixed value, with pre-attached files', () => {
const privateKeyId = 'key-67890';
const serviceAccount = 'service2@questetra.com';
const filesToDownload = [
prepareFileData('id-1', 'ファイル名', 'Hello, World!')
];
const fileIdsStr = filesToDownload.map(file => file.id).join('\n');
const preattachedFiles = new java.util.ArrayList();
preattachedFiles.add(engine.createQfile('test.html', 'text/html', ''));
preattachedFiles.add(engine.createQfile('test.xml', 'text/xml', 'さようなら'));
const fileDef = prepareConfigs(SCOPE, privateKeyId, PRIVATE_KEY, serviceAccount, 'dummyFileId', preattachedFiles);
// ファイル ID を固定値で指定
configs.put('conf_FileIds', fileIdsStr);
const token = 'token1';
let reqCount = 0;
httpClient.setRequestHandler((request) => {
const fileData = filesToDownload[Math.floor(reqCount / REQUEST_NUM_PER_FILE)];
if (reqCount % REQUEST_NUM_PER_FILE === 0 || reqCount % REQUEST_NUM_PER_FILE === 2) {
assertTokenRequest(request, SCOPE, serviceAccount);
reqCount++;
return httpClient.createHttpResponse(200, 'application/json', JSON.stringify({
'access_token': token
}));
}
if (reqCount % REQUEST_NUM_PER_FILE === 1) { // メタデータ取得のリクエスト
assertGetMetadataRequest(request, token, fileData.id);
reqCount++;
return httpClient.createHttpResponse(200, 'application/json', JSON.stringify({
'name': fileData.name
}));
}
// ファイルエクスポートのリクエスト
assertExportFileRequest(request, token, fileData.id);
reqCount++;
return httpClient.createHttpResponse(200, CONTENT_TYPE, fileData.body);
});
expect(main()).toEqual(undefined);
expect(reqCount).toEqual(filesToDownload.length * REQUEST_NUM_PER_FILE);
// ファイルのテスト
const files = engine.findData(fileDef);
expect(files.size()).toEqual(filesToDownload.length + 2); // 事前にファイルが 2 つ添付されている
filesToDownload.forEach((expectedFile, i) => assertFile(files[i + 2], expectedFile));
});
]]>