2022-10-17
(C) Questetra, Inc. (MIT License)
2
https://support.questetra.com/bpmn-icons/service-task-stripe-product-search/
https://support.questetra.com/ja/bpmn-icons/service-task-stripe-product-search/
This item searches for product objects on Stripe using a query.
この工程は、検索クエリに合致する Stripe 上の商品オブジェクトを検索します。
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAvJJREFUWEfF
l19I01EUxz/XKZqWNbVS6cH+UfSUGQlCD0EQRCkoFDkDUzENi6IgkB4sKEgjDStFnVlzalC9+BBR
IEFBEeVbFP3R/lhRpLRpKHO78dtv021u+pszfnv87Zzv+dxzzj33XoHOP6E1fvleGe9KJF/CDiSZ
CDIAo8d/BMkggn4BfVE27rX0ir9atOcEKC6WqdEOTkuoAOK0iALjAponY7jY0SF+zOYzK0CJSVYI
uAQkaAwcaDYm4VS7VTSH8g8JUFokryOpnGdgfzdBk7lTHAmmFRSgxCS7BBxYkOAeEQnd7VZRGKg5
A2BBVz4z2oxM+AF4at60kCsP1JJQ6dsTUwBKtxscvI+g4bRyjzljWOfdHVMAZSZZL+G4VpVI7AQ0
tFnFCUXDDaAMGWciv8PY55HEV3zHDTaSlWHlBigzySIJlvmoxsbBxHj4ngIOtllFpxug1CTNQMls
MjnbIS0d7t6etmpshe9DcKEmfACg3WwVpSpAoXyFIDNQZuMm2J2rfk1Ng6XL4NlTSE4Blwt6LOB0
QtEhkBKGvsCdHli9FnLzoe8h9L8MASfpN3eJLd4MDPscLFMeTTcgOhocDjXNi5fAr5+wYqUa+NOA
ClF9FiYnVdtHD2DnLpiYgNhYqKmGr5+DQoyYrSLJCyCDmdQ1gtEIdpv6r7LKbgscroKr9ZC/Dzpa
VIBr9VBeBYYoiDKotkLA/V7/svnGMVuFYuLugaAAx05C+ipIWa4KShe8eA7ZOdB4GQr2TwMMfoSM
NfDuLazfALY/YLdD9y148zp4GXwBgpag9goYk2BkGJ48hj15MDoK8QnQUAtZ26DrJrRaVMBRO9Sd
h7wC2JylAp87A9+G5ipBiCbU0ttbs6HiqFqCkA0XTCigCefchqFgFsWrO2TggxZcP5vpbRjJIAo7
rMfBbxDpPoo941i/w0gB0P04ViB0vZB4m0nXK5kXQtdL6X/JRLjXcp9M6Pcw8ULo+jTznXK6PU7n
O2q1+v0D2nRCMMki7aoAAAAASUVORK5CYII=
{
configs.put('conf_Auth', 'Stripe');
configs.put('conf_Query', query);
// 商品の ID 一覧を保存する文字型データ項目(複数行)を準備し、設定
const idDef = engine.createDataDefinition('商品 ID の一覧', 1, 'q_productIds', 'STRING_TEXTAREA');
engine.setData(idDef, '事前文字列');
configs.putObject('conf_ProductIds', idDef);
// 商品詳細ページの URL 一覧を保存する文字型データ項目(複数行)を準備し、設定
const urlDef = engine.createDataDefinition('商品 URL の一覧', 2, 'q_productUrls', 'STRING_TEXTAREA');
engine.setData(urlDef, '事前文字列');
configs.putObject('conf_ProductUrls', urlDef);
// 商品名の一覧を保存する文字型データ項目(複数行)を準備し、設定
const nameDef = engine.createDataDefinition('商品名の一覧', 3, 'q_productNames', 'STRING_TEXTAREA');
engine.setData(nameDef, '事前文字列');
configs.putObject('conf_ProductNames', nameDef);
// デフォルトの商品価格 ID の一覧を保存する文字型データ項目(複数行)を準備し、設定
const priceIdDef = engine.createDataDefinition('商品価格 ID の一覧', 4, 'q_priceIds', 'STRING_TEXTAREA');
engine.setData(priceIdDef, '事前文字列');
configs.putObject('conf_DefaultPriceIds', priceIdDef);
return {idDef, urlDef, nameDef, priceIdDef};
}
const SAMPLE_QUERY_1 = 'name~"テスト商品"';
const SAMPLE_QUERY_2 = 'active:"true"\nAND metadata["key"]:"value"'; // 複数行
/**
* 保存先データ項目がひとつも設定されていない
*/
test('Search query is blank', () => {
prepareConfigs(SAMPLE_QUERY_1);
configs.put('conf_ProductIds', '');
configs.put('conf_ProductUrls', '');
configs.put('conf_ProductNames', '');
configs.put('conf_DefaultPriceIds', '');
expect(execute).toThrow('No data item to save the search result is set.');
});
/**
* 保存先データ項目が重複 - ID と URL
*/
test('Same data item is set multiple times - ID and URL', () => {
const {idDef} = prepareConfigs(SAMPLE_QUERY_1);
configs.putObject('conf_ProductUrls', idDef);
expect(execute).toThrow('Same data item is set multiple times.');
});
/**
* 保存先データ項目が重複 - URL と名前
*/
test('Same data item is set multiple times - URL and Name', () => {
const {urlDef} = prepareConfigs(SAMPLE_QUERY_1);
configs.putObject('conf_ProductNames', urlDef);
expect(execute).toThrow('Same data item is set multiple times.');
});
/**
* 保存先データ項目が重複 - 名前と商品価格 ID
*/
test('Same data item is set multiple times - Name and Price ID', () => {
const {nameDef} = prepareConfigs(SAMPLE_QUERY_1);
configs.putObject('conf_DefaultPriceIds', nameDef);
expect(execute).toThrow('Same data item is set multiple times.');
});
/**
* 保存先データ項目が重複 - ID と商品価格 ID
*/
test('Same data item is set multiple times - ID and Price ID', () => {
const {priceIdDef} = prepareConfigs(SAMPLE_QUERY_1);
configs.putObject('conf_ProductIds', priceIdDef);
expect(execute).toThrow('Same data item is set multiple times.');
});
/** 1 リクエストで取得できる件数 */
const MAX_RESULTS_PER_REQ = 100;
/**
* 商品を検索する API リクエストのテスト
* @param {Object} request
* @param request.url
* @param request.method
* @param request.headers
* @param query
* @param page
*/
const assertRequest = ({url, method, headers}, query, page) => {
const encodedQuery = encodeURIComponent(query).replace(/~/g, '%7E').replace(/%20/g, '+');
let expectedQueryStr = `query=${encodedQuery}&limit=${MAX_RESULTS_PER_REQ}`;
if (page !== null) {
expectedQueryStr += `&page=${page}`;
}
expect(url).toEqual(`https://api.stripe.com/v1/products/search?${expectedQueryStr}`);
expect(method).toEqual('GET');
expect(headers.get('Stripe-Version')).toEqual(STRIPE_VERSION);
};
/**
* 初回の商品を検索する HTTP リクエストで失敗
*/
test('Fail to search products', () => {
prepareConfigs(SAMPLE_QUERY_1);
httpClient.setRequestHandler((request) => {
assertRequest(request, SAMPLE_QUERY_1, null);
return httpClient.createHttpResponse(400, 'application/json', '{}');
});
expect(execute).toThrow('Failed to search products. status: 400');
});
/**
* 検索結果が 0 件
*/
test('No products found', () => {
prepareConfigs(SAMPLE_QUERY_1);
httpClient.setRequestHandler((request) => {
assertRequest(request, SAMPLE_QUERY_1, null);
return httpClient.createHttpResponse(200, 'application/json', '{"data": []}');
});
expect(execute).toThrow('No products found.');
});
/**
* あたえられた数の商品オブジェクトの配列を返す
* @param number
* @return products
*/
const prepareProducts = (number) => {
const products = [];
for (let i = 0; i < number; i++) {
const product = {
'id': `prod_${i+1}`,
'name': `テスト商品 ${i+1}`,
'default_price': `price_${i+1}`
};
products.push(product);
}
return products;
};
/**
* 検索 API のレスポンスボディを返す
* @param hasMore
* @param results
* @param nextPage
* @return response
*/
const searchResponse = (hasMore, results, nextPage) => {
const responseObj = {
'has_more': hasMore,
'data': results
};
if (nextPage !== null) {
responseObj['next_page'] = nextPage;
}
return JSON.stringify(responseObj);
};
/**
* 成功 - 検索結果が 1 件、単一行データ項目に保存
*/
test('Success - 1 product found, saved to single-line data items', () => {
prepareConfigs(SAMPLE_QUERY_1);
// 商品の ID 一覧を保存する文字型データ項目(単一行)を準備し、設定
const idDef = engine.createDataDefinition('商品 ID の一覧', 5, 'q_productId', 'STRING_TEXTFIELD');
engine.setData(idDef, '事前文字列');
configs.putObject('conf_ProductIds', idDef);
// 商品詳細ページの URL 一覧を保存する文字型データ項目(単一行)を準備し、設定
const urlDef = engine.createDataDefinition('商品 URL の一覧', 6, 'q_productUrl', 'STRING_TEXTFIELD');
engine.setData(urlDef, '事前文字列');
configs.putObject('conf_ProductUrls', urlDef);
// 商品名の一覧を保存する文字型データ項目(単一行)を準備し、設定
const nameDef = engine.createDataDefinition('商品名の一覧', 7, 'q_productName', 'STRING_TEXTFIELD');
engine.setData(nameDef, '事前文字列');
configs.putObject('conf_ProductNames', nameDef);
// 商品価格 ID の一覧を保存する文字型データ項目(単一行)を準備し、設定
const priceIdDef = engine.createDataDefinition('商品価格 ID の一覧', 8, 'q_priceId', 'STRING_TEXTFIELD');
engine.setData(priceIdDef, '事前文字列');
configs.putObject('conf_DefaultPriceIds', priceIdDef);
const results = prepareProducts(1);
httpClient.setRequestHandler((request) => {
assertRequest(request, SAMPLE_QUERY_1, null);
return httpClient.createHttpResponse(200, 'application/json', searchResponse(false, results, null));
});
execute();
// 文字型データ項目の値をチェック
expect(engine.findData(idDef)).toEqual('prod_1');
expect(engine.findData(urlDef)).toEqual('https://dashboard.stripe.com/products/prod_1');
expect(engine.findData(nameDef)).toEqual('テスト商品 1');
expect(engine.findData(priceIdDef)).toEqual('price_1');
});
const REQ_LIMIT = httpClient.getRequestingLimit();
/**
* 成功 - 検索結果が上限件。デフォルトの商品価格 ID が未設定の商品がある
*/
test('Success - max number of products found, with some default_price null', () => {
const {idDef, urlDef, nameDef, priceIdDef} = prepareConfigs(SAMPLE_QUERY_2);
const resultNum = MAX_RESULTS_PER_REQ * REQ_LIMIT;
const results = prepareProducts(resultNum);
results[2].default_price = null;
results[3].default_price = null;
results[10].default_price = null;
let reqCount = 0;
httpClient.setRequestHandler((request) => {
const start = MAX_RESULTS_PER_REQ * reqCount;
const end = start + MAX_RESULTS_PER_REQ;
if (reqCount === 0) {
assertRequest(request, SAMPLE_QUERY_2, null);
reqCount++;
const response = searchResponse(true, results.slice(start, end), `page_${reqCount}`);
return httpClient.createHttpResponse(200, 'application/json', response);
}
assertRequest(request, SAMPLE_QUERY_2, `page_${reqCount}`);
if (reqCount < REQ_LIMIT - 1) {
reqCount++;
const response = searchResponse(true, results.slice(start, end), `page_${reqCount}`);
return httpClient.createHttpResponse(200, 'application/json', response);
}
const response = searchResponse(false, results.slice(start, end), null);
return httpClient.createHttpResponse(200, 'application/json', response);
});
execute();
// 文字型データ項目の値をチェック
const ids = engine.findData(idDef).split('\n');
const urls = engine.findData(urlDef).split('\n');
const names = engine.findData(nameDef).split('\n');
const priceIds = engine.findData(priceIdDef).split('\n');
expect(ids.length).toEqual(resultNum);
expect(urls.length).toEqual(resultNum);
expect(names.length).toEqual(resultNum);
expect(priceIds.length).toEqual(resultNum);
for (let i = 0; i < resultNum; i++) {
expect(ids[i]).toEqual(`prod_${i+1}`);
expect(urls[i]).toEqual(`https://dashboard.stripe.com/products/prod_${i+1}`);
expect(names[i]).toEqual(`テスト商品 ${i+1}`);
expect(names[i]).toEqual(`テスト商品 ${i+1}`);
if (i === 2 || i === 3 || i === 10) {
expect(priceIds[i]).toEqual(''); // 空行
} else {
expect(priceIds[i]).toEqual(`price_${i+1}`);
}
}
});
/**
* 検索クエリが空で、全件取得。検索結果が多すぎて HTTP リクエスト数の上限を超える
*/
test('Search query is blank. Number of HTTP requests exceeds the limit', () => {
const {idDef, urlDef, nameDef, priceIdDef} = prepareConfigs('');
const resultNum = MAX_RESULTS_PER_REQ * REQ_LIMIT;
const results = prepareProducts(resultNum);
let reqCount = 0;
const query = 'active:"true" OR active:"false"';
httpClient.setRequestHandler((request) => {
const start = MAX_RESULTS_PER_REQ * reqCount;
const end = start + MAX_RESULTS_PER_REQ;
if (reqCount === 0) {
assertRequest(request, query, null);
reqCount++;
const response = searchResponse(true, results.slice(start, end), `page_${reqCount}`);
return httpClient.createHttpResponse(200, 'application/json', response);
}
assertRequest(request, query, `page_${reqCount}`);
reqCount++;
// 最後のリクエストでも hasMore が true
const response = searchResponse(true, results.slice(start, end), `page_${reqCount}`);
return httpClient.createHttpResponse(200, 'application/json', response);
});
expect(execute).toThrow(`HTTP requests must be up to ${REQ_LIMIT}.`); // スクリプトエンジンのエラー
});
/**
* 検索結果が複数件で、保存先データ項目が単一行 - ID
*/
test('Cannot save multiple results to single-line data item - ID', () => {
prepareConfigs(SAMPLE_QUERY_1);
// 商品の ID 一覧を保存する文字型データ項目(単一行)を準備し、設定
const idDef = engine.createDataDefinition('商品 ID の一覧', 5, 'q_productId', 'STRING_TEXTFIELD');
configs.putObject('conf_ProductIds', idDef);
const results = prepareProducts(2);
httpClient.setRequestHandler((request) => {
assertRequest(request, SAMPLE_QUERY_1, null);
return httpClient.createHttpResponse(200, 'application/json', searchResponse(false, results, null));
});
expect(execute).toThrow('More than one products were found while the data item to save the result is Single-Line.');
});
/**
* 検索結果が複数件で、保存先データ項目が単一行 - URL
*/
test('Cannot save multiple results to single-line data item - URL', () => {
prepareConfigs(SAMPLE_QUERY_2);
// 商品詳細ページの URL 一覧を保存する文字型データ項目(単一行)を準備し、設定
const urlDef = engine.createDataDefinition('商品 URL の一覧', 6, 'q_productUrl', 'STRING_TEXTFIELD');
configs.putObject('conf_ProductUrls', urlDef);
const resultNum = MAX_RESULTS_PER_REQ + 1; // 1 回のリクエストでは取得しきれない件数
const results = prepareProducts(resultNum);
httpClient.setRequestHandler((request) => { // 1 回目のリクエストのあとエラーになるので、リクエストは 1 回のみ
assertRequest(request, SAMPLE_QUERY_2, null);
const response = searchResponse(true, results.slice(0, MAX_RESULTS_PER_REQ), 'next_page')
return httpClient.createHttpResponse(200, 'application/json', response);
});
expect(execute).toThrow('More than one products were found while the data item to save the result is Single-Line.');
});
/**
* 検索結果が複数件で、保存先データ項目が単一行 - 名前
*/
test('Cannot save multiple results to single-line data item - Name', () => {
prepareConfigs(SAMPLE_QUERY_1);
// 商品名の一覧を保存する文字型データ項目(単一行)を準備し、設定
const nameDef = engine.createDataDefinition('商品名の一覧', 7, 'q_productName', 'STRING_TEXTFIELD');
configs.putObject('conf_ProductNames', nameDef);
const results = prepareProducts(2);
httpClient.setRequestHandler((request) => {
assertRequest(request, SAMPLE_QUERY_1, null);
return httpClient.createHttpResponse(200, 'application/json', searchResponse(false, results, null));
});
expect(execute).toThrow('More than one products were found while the data item to save the result is Single-Line.');
});
/**
* 検索結果が複数件で、保存先データ項目が単一行 - 商品価格 ID
*/
test('Cannot save multiple results to single-line data item - Price ID', () => {
prepareConfigs(SAMPLE_QUERY_2);
// 商品価格 ID の一覧を保存する文字型データ項目(単一行)を準備し、設定
const priceIdDef = engine.createDataDefinition('商品価格 ID の一覧', 8, 'q_priceId', 'STRING_TEXTFIELD');
configs.putObject('conf_DefaultPriceIds', priceIdDef);
const results = prepareProducts(2);
httpClient.setRequestHandler((request) => {
assertRequest(request, SAMPLE_QUERY_2, null);
return httpClient.createHttpResponse(200, 'application/json', searchResponse(false, results, null));
});
expect(execute).toThrow('More than one products were found while the data item to save the result is Single-Line.');
});
/**
* 成功 - ID と商品価格 ID の保存先データ項目が未設定
*/
test('Success - Data item to save IDs and Price IDs are not set', () => {
const {idDef, urlDef, nameDef, priceIdDef} = prepareConfigs(SAMPLE_QUERY_1);
configs.put('conf_ProductIds', '');
configs.put('conf_DefaultPriceIds', '');
const resultNum = MAX_RESULTS_PER_REQ;
const results = prepareProducts(resultNum);
httpClient.setRequestHandler((request) => {
assertRequest(request, SAMPLE_QUERY_1, null);
return httpClient.createHttpResponse(200, 'application/json', searchResponse(false, results, null));
});
execute();
// 文字型データ項目の値をチェック
expect(engine.findData(idDef)).toEqual('事前文字列');
expect(engine.findData(priceIdDef)).toEqual('事前文字列');
const urls = engine.findData(urlDef).split('\n');
const names = engine.findData(nameDef).split('\n');
expect(urls.length).toEqual(resultNum);
expect(names.length).toEqual(resultNum);
for (let i = 0; i < resultNum; i++) {
expect(urls[i]).toEqual(`https://dashboard.stripe.com/products/prod_${i+1}`);
expect(names[i]).toEqual(`テスト商品 ${i+1}`);
}
});
/**
* 成功 - URL と名前の保存先データ項目が未設定
*/
test('Success - Data item to save URLs and Names are not set', () => {
const {idDef, urlDef, nameDef, priceIdDef} = prepareConfigs(SAMPLE_QUERY_2);
configs.put('conf_ProductUrls', '');
configs.put('conf_ProductNames', '');
const resultNum = 10;
const results = prepareProducts(resultNum);
httpClient.setRequestHandler((request) => {
assertRequest(request, SAMPLE_QUERY_2, null);
return httpClient.createHttpResponse(200, 'application/json', searchResponse(false, results, null));
});
execute();
// 文字型データ項目の値をチェック
expect(engine.findData(urlDef)).toEqual('事前文字列');
expect(engine.findData(nameDef)).toEqual('事前文字列');
const ids = engine.findData(idDef).split('\n');
const priceIds = engine.findData(priceIdDef).split('\n');
expect(ids.length).toEqual(resultNum);
expect(priceIds.length).toEqual(resultNum);
for (let i = 0; i < resultNum; i++) {
expect(ids[i]).toEqual(`prod_${i+1}`);
expect(priceIds[i]).toEqual(`price_${i+1}`);
}
});
]]>