Skip to content

Instantly share code, notes, and snippets.

@sakaguchi-diverta
Last active April 10, 2025 03:25
Show Gist options
  • Save sakaguchi-diverta/2fbedcc430366126fea46d0f5a127a23 to your computer and use it in GitHub Desktop.
Save sakaguchi-diverta/2fbedcc430366126fea46d0f5a127a23 to your computer and use it in GitHub Desktop.
openapiToKurocoPostman
/**
* Prerequisites
* ```
* npm i openapi-to-postmanv2@^5.0.0
* ```
*/
const fs = require('fs');
const Converter = require('openapi-to-postmanv2');
/**
* OpenAPIの全てのエンドポイントのタグを'テスト'に設定する
* @param {Object} openapi - OpenAPI仕様オブジェクト
* @returns {Object} - タグを更新したOpenAPI仕様オブジェクト
*/
function updateTags(openapi) {
Object.keys(openapi.paths).forEach(path => {
Object.keys(openapi.paths[path]).forEach(method => {
const operation = openapi.paths[path][method];
operation.tags = ['テスト'];
});
});
return openapi;
}
/**
* アイテムからidを削除する再帰関数
* @param {Object} item - Postman Collectionのアイテム
*/
function removeIds(item) {
if (item.id) {
delete item.id;
}
if (item.item && Array.isArray(item.item)) {
item.item.forEach(removeIds);
}
}
/**
* リクエストにテストスクリプトを追加する
* @param {Object} request - Postman Collectionのリクエスト
*/
function addTestScript(request) {
if (!request.event) {
request.event = [];
}
request.event.push({
listen: "test",
script: {
exec: [
"pm.test(\"Save current response as sample\", function () {",
` const response = pm.response.json();`,
` pm.collectionVariables.set(\"${request.name}\", JSON.stringify(response));`,
` pm.collectionVariables.set(\"[サンプル保存時刻]${request.name}\", (new Date()).toLocaleString());`,
"});",
""
],
type: "text/javascript",
packages: {}
}
});
}
/**
* リクエストにレスポンステストスクリプトを追加する
* @param {Object} request - Postman Collectionのリクエスト
*/
function addResponseTestScript(request) {
if (!request.event) {
request.event = [];
}
request.event.push({
listen: "test",
script: {
exec: [
"pm.test(\"レスポンスが事前に保存したサンプルと一致すること\", function () {",
" const current = pm.response.json();",
` const sample = JSON.parse(pm.collectionVariables.get(\"${request.name}\"));`,
"",
" const ignore_keys = [",
" ];",
"",
" const equalRecursive = eval(pm.collectionVariables.get(\"equalRecursive\"));",
" equalRecursive(sample, current, pm, '', ignore_keys);",
"});"
],
type: "text/javascript",
packages: {}
}
});
}
/**
* クエリパラメータを無効化し、response配列を削除する
* @param {Object} request - Postman Collectionのリクエスト
*/
function cleanRequest(request) {
if (request.request && request.request.url) {
// クエリパラメータを無効化
if (request.request.url.query) {
request.request.url.query.forEach(param => {
param.disabled = true;
});
}
}
// response配列を削除
if (request.response) {
delete request.response;
}
}
/**
* OpenAPI仕様をPostman Collectionに変換する
* @param {Object} openapi - OpenAPI仕様オブジェクト
* @param {string} outputFile - 出力ファイル名
*/
function convertToPostmanCollection(openapi, outputFile) {
const options = {
folderStrategy: 'Tags',
requestNameSource: 'summary',
indentCharacter: ' '
};
Converter.convert(
{ type: 'json', data: openapi },
options,
(err, conversionResult) => {
if (err) {
console.error('変換エラー:', err);
return;
}
if (!conversionResult.result) {
console.error('変換失敗:', conversionResult.reason);
return;
}
const collection = conversionResult.output[0].data;
// 各リクエストのauth設定の{{apiKey}}を{{vault:RCMS_API_STATIC_TOKEN}}に変換
if (collection.item) {
collection.item.forEach(folder => {
if (folder.item) {
folder.item.forEach(request => {
if (request.request && request.request.auth && request.request.auth.apikey) {
request.request.auth.apikey.forEach(item => {
if (item.key === 'value' && item.value === '{{apiKey}}') {
item.value = '{{vault:RCMS_API_STATIC_TOKEN}}';
}
});
}
});
}
});
// "テスト"フォルダをコピーして"サンプル保存"として追加
const testFolder = collection.item.find(folder => folder.name === 'テスト');
if (testFolder) {
const sampleFolder = JSON.parse(JSON.stringify(testFolder));
sampleFolder.name = 'サンプル保存';
collection.item.unshift(sampleFolder);
// サンプル保存フォルダの各リクエストにテストスクリプトを追加
if (sampleFolder.item) {
sampleFolder.item.forEach(request => {
addTestScript(request);
});
}
// テストフォルダに共通pre-requestスクリプトを追加
if (!testFolder.event) {
testFolder.event = [];
}
testFolder.event.push({
listen: "prerequest",
script: {
exec: [
"const equalRecursive = `(expected, actual, pm, _keys, ignore_keys) => {",
" const normKeys = _keys.replace(/\\[[0-9]+\\]/, '[]');",
" if (ignore_keys.includes(normKeys)){",
" // console.log('ignored :' + _keys);",
" return;",
" }",
" if (typeof pm === 'undefined'){",
" console.log('pm is undefined.');",
" return;",
" }",
" try {",
" pm.expect(typeof expected).to.eql(typeof actual);",
" } catch(e) {",
" console.error('expect type is:' + (typeof expected) + '. but actual type:' + (typeof actual) + '. key:' + _keys + '.' + key);",
" throw e;",
" }",
" if (typeof expected !== typeof actual) {",
" return;",
" }",
" if (expected === null){",
" try {",
" pm.expect(null).to.eql(actual);",
" } catch(e) {",
" console.error('expect null but actual:' + actual + '. key:' + _keys + '.' + key);",
" throw e;",
" }",
" } else if (Array.isArray(expected)) {",
" expected.forEach((expectedItem, idx) => {",
" const actualItem = actual[idx];",
" const keys = _keys + '[' + idx + ']';",
" equalRecursive(expectedItem, actualItem, pm, keys,ignore_keys);",
" });",
" } else if (typeof expected === 'object') {",
" pm.expect('object').to.eql(typeof actual);",
" Object.keys(expected).forEach((key) => {",
" try {",
" pm.expect(actual.hasOwnProperty(key)).to.eql(true);",
" }catch(e){",
" console.error('actual do not have key:' + _keys + '.' + key);",
" throw e;",
" }",
" const expectedItem = expected[key];",
" const actualItem = actual[key];",
" const keys = _keys + '.' + key;",
" equalRecursive(expectedItem, actualItem, pm, keys,ignore_keys);",
" });",
" Object.keys(actual).forEach((key) => {",
" try {",
" pm.expect(expected.hasOwnProperty(key)).to.eql(true);",
" }catch(e){",
" console.error('expected do not have key:' + _keys + '.' + key);",
" throw e;",
" }",
" });",
" } else if (",
" typeof expected === 'number' ",
" || typeof expected === 'string'",
" ) {",
" try {",
" pm.expect(expected).to.eql(actual);",
" // console.log('[OK]' + _keys + ' expected: ' + expected + ' actual:' + actual);",
" }catch (e){",
" console.error(_keys + ' is NG.expected: ' + expected + ' actual: ' + actual);",
" throw e;",
" }",
" } else {",
" return;",
" }",
"}`;",
"if (typeof pm.collectionVariables.get(\"equalRecursive\") === 'undefined') {",
" pm.test(\"共通関数を準備します。\", function () {",
" pm.collectionVariables.set(\"equalRecursive\", equalRecursive);",
" });",
"}"
],
type: "text/javascript",
packages: {}
}
});
// テストフォルダの各リクエストにレスポンステストスクリプトを追加
if (testFolder.item) {
testFolder.item.forEach(request => {
addResponseTestScript(request);
});
}
}
// すべてのアイテムからidを削除し、クエリパラメータとresponseをクリーンアップ
collection.item.forEach(folder => {
if (folder.item) {
folder.item.forEach(request => {
removeIds(request);
cleanRequest(request);
});
}
removeIds(folder);
});
}
fs.writeFileSync(
outputFile,
JSON.stringify(collection, null, 2),
'utf8'
);
console.log(`Postman Collectionに変換し、${outputFile}に保存しました。`);
}
);
}
// コマンドライン引数の処理
const args = process.argv.slice(2);
const inputFile = args[0] || 'openapi.json';
const outputFile = args[1] || 'postman_collection.json';
// メイン処理
try {
const openapiFile = fs.readFileSync(inputFile, 'utf8');
const openapi = JSON.parse(openapiFile);
// タグを更新
const updatedOpenapi = updateTags(openapi);
const updatedOpenapiFile = 'openapi_test.json';
fs.writeFileSync(updatedOpenapiFile, JSON.stringify(updatedOpenapi, null, 2), 'utf8');
// Postman Collectionに変換
convertToPostmanCollection(updatedOpenapi, outputFile);
console.log(`Postman Collectionの変換が完了しました: ${outputFile}`);
} catch (error) {
console.error('エラーが発生しました:', error.message);
process.exit(1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment