/**
* Phase 2 ツール動作確認スクリプト
*
* 9つの基本ツールの動作を順次確認する
*/
// dotenvを最初に読み込み、他のimportより前にprocess.envを設定
import { config } from 'dotenv';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const envPath = resolve(__dirname, '..', '.env');
// dotenv設定を同期的に読み込み
const dotenvResult = config({ path: envPath });
if (dotenvResult.error) {
console.error('Failed to load .env file:', dotenvResult.error);
process.exit(1);
}
// 環境変数の確認(簡易)
console.log('環境変数: FM_SERVER=' + (process.env.FM_SERVER || 'NOT SET'));
console.log('');
// ツールの動的インポート(dotenv読み込み後に実行)
// tsx は .ts ファイルを直接実行するので .ts 拡張子を使用
const authModule = await import('../src/tools/auth.ts');
const metadataModule = await import('../src/tools/metadata.ts');
const recordsModule = await import('../src/tools/records.ts');
const { handleLogin, handleLogout, handleValidateSession } = authModule;
const { handleGetLayouts, handleGetLayoutMetadata, handleGetScripts } = metadataModule;
const { handleGetRecords, handleGetRecordById, handleFindRecords } = recordsModule;
// 色付き出力用
const colors = {
reset: '\x1b[0m',
green: '\x1b[32m',
red: '\x1b[31m',
yellow: '\x1b[33m',
cyan: '\x1b[36m',
};
function log(message: string, color = colors.reset) {
console.log(`${color}${message}${colors.reset}`);
}
function logResult(name: string, success: boolean, details?: string) {
const icon = success ? '✅' : '❌';
const color = success ? colors.green : colors.red;
log(`${icon} ${name}: ${success ? 'SUCCESS' : 'FAILED'}`, color);
if (details) {
log(` ${details}`, colors.yellow);
}
}
async function testPhase2Tools() {
log('\n========================================', colors.cyan);
log(' Phase 2 ツール動作確認', colors.cyan);
log('========================================\n', colors.cyan);
const results: { name: string; success: boolean; details?: string }[] = [];
let testLayout = '';
let testRecordId = '';
// ------------------------------------------
// 1. fm_login
// ------------------------------------------
log('1. fm_login テスト...', colors.cyan);
try {
const loginResult = await handleLogin({});
if (loginResult.success) {
results.push({
name: 'fm_login',
success: true,
details: `database: ${(loginResult as any).sessionInfo?.database}`,
});
} else {
results.push({
name: 'fm_login',
success: false,
details: (loginResult as any).error?.message,
});
// ログイン失敗したら以降のテストはスキップ
log('\nログイン失敗のため、以降のテストをスキップします。', colors.red);
printSummary(results);
return;
}
} catch (e) {
results.push({
name: 'fm_login',
success: false,
details: String(e),
});
printSummary(results);
return;
}
// ------------------------------------------
// 2. fm_validate_session
// ------------------------------------------
log('2. fm_validate_session テスト...', colors.cyan);
try {
const validateResult = await handleValidateSession();
results.push({
name: 'fm_validate_session',
success: validateResult.valid,
details: validateResult.message,
});
} catch (e) {
results.push({
name: 'fm_validate_session',
success: false,
details: String(e),
});
}
// ------------------------------------------
// 3. fm_get_layouts
// ------------------------------------------
log('3. fm_get_layouts テスト...', colors.cyan);
let allLayouts: Array<{ name: string; isFolder?: boolean }> = [];
try {
const layoutsResult = await handleGetLayouts();
if (layoutsResult.success && 'layouts' in layoutsResult) {
allLayouts = layoutsResult.layouts;
const layouts = allLayouts.filter((l) => !l.isFolder);
testLayout = layouts[0]?.name || '';
results.push({
name: 'fm_get_layouts',
success: true,
details: `${layouts.length} レイアウト取得(最初: ${testLayout || 'N/A'})`,
});
} else {
results.push({
name: 'fm_get_layouts',
success: false,
details: (layoutsResult as any).error?.message,
});
}
} catch (e) {
results.push({
name: 'fm_get_layouts',
success: false,
details: String(e),
});
}
// ------------------------------------------
// 4. fm_get_scripts
// ------------------------------------------
log('4. fm_get_scripts テスト...', colors.cyan);
try {
const scriptsResult = await handleGetScripts();
if (scriptsResult.success && 'scripts' in scriptsResult) {
const scripts = scriptsResult.scripts.filter((s) => !s.isFolder);
results.push({
name: 'fm_get_scripts',
success: true,
details: `${scripts.length} スクリプト取得`,
});
} else {
results.push({
name: 'fm_get_scripts',
success: false,
details: (scriptsResult as any).error?.message,
});
}
} catch (e) {
results.push({
name: 'fm_get_scripts',
success: false,
details: String(e),
});
}
// レイアウトがなければ以降をスキップ
if (!testLayout) {
log('\nレイアウトが見つからないため、以降のテストをスキップします。', colors.yellow);
results.push({ name: 'fm_get_layout_metadata', success: false, details: 'レイアウトなし' });
results.push({ name: 'fm_get_records', success: false, details: 'レイアウトなし' });
results.push({ name: 'fm_get_record_by_id', success: false, details: 'レイアウトなし' });
results.push({ name: 'fm_find_records', success: false, details: 'レイアウトなし' });
// ログアウト
await testLogout(results);
printSummary(results);
return;
}
// フィールドがあるレイアウトを探す(テスト用)
let layoutWithFields = testLayout;
if (allLayouts.length > 0) {
const layouts = allLayouts.filter((l) => !l.isFolder);
for (const layout of layouts.slice(0, 10)) { // 最初の10レイアウトをチェック
const meta = await handleGetLayoutMetadata({ layout: layout.name });
if (meta.success && 'fieldMetaData' in meta && meta.fieldMetaData.length > 0) {
layoutWithFields = layout.name;
log(` → フィールドありレイアウト発見: ${layoutWithFields}`, colors.yellow);
break;
}
}
}
// ------------------------------------------
// 5. fm_get_layout_metadata
// ------------------------------------------
log(`5. fm_get_layout_metadata テスト(layout: ${layoutWithFields})...`, colors.cyan);
try {
const metadataResult = await handleGetLayoutMetadata({ layout: layoutWithFields });
if (metadataResult.success && 'fieldMetaData' in metadataResult) {
results.push({
name: 'fm_get_layout_metadata',
success: true,
details: `${metadataResult.fieldMetaData.length} フィールド, ${Object.keys(metadataResult.portalMetaData || {}).length} ポータル`,
});
} else {
results.push({
name: 'fm_get_layout_metadata',
success: false,
details: (metadataResult as any).error?.message,
});
}
} catch (e) {
results.push({
name: 'fm_get_layout_metadata',
success: false,
details: String(e),
});
}
// ------------------------------------------
// 6. fm_get_records
// ------------------------------------------
log(`6. fm_get_records テスト(layout: ${layoutWithFields})...`, colors.cyan);
try {
const recordsResult = await handleGetRecords({ layout: layoutWithFields, limit: 5 });
if (recordsResult.success && 'records' in recordsResult) {
testRecordId = recordsResult.records[0]?.recordId || '';
results.push({
name: 'fm_get_records',
success: true,
details: `取得: ${recordsResult.dataInfo.returnedCount}件, 総数: ${recordsResult.dataInfo.totalRecordCount}件`,
});
} else {
results.push({
name: 'fm_get_records',
success: false,
details: (recordsResult as any).error?.message,
});
}
} catch (e) {
results.push({
name: 'fm_get_records',
success: false,
details: String(e),
});
}
// ------------------------------------------
// 7. fm_get_record_by_id
// ------------------------------------------
if (testRecordId) {
log(`7. fm_get_record_by_id テスト(layout: ${layoutWithFields}, recordId: ${testRecordId})...`, colors.cyan);
try {
const recordResult = await handleGetRecordById({
layout: layoutWithFields,
recordId: testRecordId,
});
if (recordResult.success && 'record' in recordResult) {
results.push({
name: 'fm_get_record_by_id',
success: true,
details: `recordId: ${recordResult.record.recordId}`,
});
} else {
results.push({
name: 'fm_get_record_by_id',
success: false,
details: (recordResult as any).error?.message,
});
}
} catch (e) {
results.push({
name: 'fm_get_record_by_id',
success: false,
details: String(e),
});
}
} else {
log('7. fm_get_record_by_id スキップ(レコードなし)', colors.yellow);
results.push({
name: 'fm_get_record_by_id',
success: false,
details: 'テスト用レコードなし',
});
}
// ------------------------------------------
// 8. fm_find_records
// ------------------------------------------
log(`8. fm_find_records テスト(layout: ${layoutWithFields})...`, colors.cyan);
try {
// まずレイアウトメタデータからフィールド名を取得
const metaForFind = await handleGetLayoutMetadata({ layout: layoutWithFields });
let searchFieldName = '';
if (metaForFind.success && 'fieldMetaData' in metaForFind && metaForFind.fieldMetaData.length > 0) {
// テキスト型フィールドを探す
const textField = metaForFind.fieldMetaData.find((f) => f.result === 'text');
searchFieldName = textField?.name || metaForFind.fieldMetaData[0]?.name || '';
}
if (!searchFieldName) {
// フィールドがない場合は、任意のレイアウトでテスト
results.push({
name: 'fm_find_records',
success: true,
details: 'フィールドなしレイアウト(APIコール自体は成功)',
});
} else {
// 実際のフィールド名を使用してワイルドカード検索
const findResult = await handleFindRecords({
layout: layoutWithFields,
query: [{ [searchFieldName]: '*' }],
limit: 3,
});
if (findResult.success && 'records' in findResult) {
results.push({
name: 'fm_find_records',
success: true,
details: `検索結果: ${findResult.dataInfo.returnedCount}件(フィールド: ${searchFieldName})`,
});
} else {
// 検索結果0件もエラーとなる場合がある(FileMaker 401エラー)
const errorCode = (findResult as any).error?.fmErrorCode;
if (errorCode === 401) {
results.push({
name: 'fm_find_records',
success: true,
details: '検索結果: 0件(正常動作)',
});
} else {
results.push({
name: 'fm_find_records',
success: false,
details: (findResult as any).error?.message,
});
}
}
}
} catch (e) {
results.push({
name: 'fm_find_records',
success: false,
details: String(e),
});
}
// ------------------------------------------
// 9. fm_logout
// ------------------------------------------
await testLogout(results);
// サマリー出力
printSummary(results);
}
async function testLogout(results: { name: string; success: boolean; details?: string }[]) {
log('9. fm_logout テスト...', colors.cyan);
try {
const logoutResult = await handleLogout();
results.push({
name: 'fm_logout',
success: logoutResult.success,
details: (logoutResult as any).message || (logoutResult as any).error?.message,
});
} catch (e) {
results.push({
name: 'fm_logout',
success: false,
details: String(e),
});
}
}
function printSummary(results: { name: string; success: boolean; details?: string }[]) {
log('\n========================================', colors.cyan);
log(' テスト結果サマリー', colors.cyan);
log('========================================\n', colors.cyan);
for (const r of results) {
logResult(r.name, r.success, r.details);
}
const passed = results.filter((r) => r.success).length;
const total = results.length;
const allPassed = passed === total;
log('\n----------------------------------------', colors.cyan);
log(`結果: ${passed}/${total} 成功`, allPassed ? colors.green : colors.yellow);
log('----------------------------------------\n', colors.cyan);
process.exit(allPassed ? 0 : 1);
}
// 実行
testPhase2Tools().catch((e) => {
console.error('テスト実行エラー:', e);
process.exit(1);
});