n8nのフォルダ/ワークフロー構成をそのままapp-portal(/n8n)に反映し、 Webhookトリガーに限らず任意のワークフローの実行履歴・ノード単位の 成功/失敗・エラー内容を確認できるようにした(get-voice-mail専用にせず n8n Public API経由で動的取得する汎用設計)。TDDで実装、テスト23件追加。
84 lines
3.3 KiB
JavaScript
84 lines
3.3 KiB
JavaScript
const { test } = require('node:test');
|
|
const assert = require('node:assert');
|
|
const { renderFolderTree, renderExecutionList, renderExecutionDetail, renderN8nPage } = require('../src/n8nView');
|
|
|
|
test('renderFolderTree renders nothing for an empty tree', () => {
|
|
assert.strictEqual(renderFolderTree([]), '');
|
|
});
|
|
|
|
test('renderFolderTree renders a workflow item with its id in a data attribute', () => {
|
|
const html = renderFolderTree([{ type: 'workflow', id: 'w1', name: 'ワークフローA', active: true }]);
|
|
assert.ok(html.includes('data-workflow-id="w1"'));
|
|
assert.ok(html.includes('ワークフローA'));
|
|
});
|
|
|
|
test('renderFolderTree marks inactive workflows differently from active ones', () => {
|
|
const activeHtml = renderFolderTree([{ type: 'workflow', id: 'w1', name: 'A', active: true }]);
|
|
const inactiveHtml = renderFolderTree([{ type: 'workflow', id: 'w2', name: 'B', active: false }]);
|
|
assert.ok(activeHtml.includes('bg-success'));
|
|
assert.ok(inactiveHtml.includes('bg-secondary'));
|
|
});
|
|
|
|
test('renderFolderTree escapes HTML in workflow and folder names', () => {
|
|
const html = renderFolderTree([{ type: 'workflow', id: 'w1', name: '<script>bad</script>', active: true }]);
|
|
assert.ok(!html.includes('<script>bad</script>'));
|
|
assert.ok(html.includes('<script>'));
|
|
});
|
|
|
|
test('renderFolderTree nests folder children inside the folder element', () => {
|
|
const html = renderFolderTree([
|
|
{
|
|
type: 'folder',
|
|
id: 'f1',
|
|
name: 'フォルダA',
|
|
children: [{ type: 'workflow', id: 'w1', name: 'ワークフローA', active: true }],
|
|
},
|
|
]);
|
|
assert.ok(html.includes('フォルダA'));
|
|
assert.ok(html.includes('data-workflow-id="w1"'));
|
|
});
|
|
|
|
test('renderExecutionList renders a row per execution with a status badge', () => {
|
|
const html = renderExecutionList([
|
|
{ id: 'e1', status: 'success', startedAt: '2026-09-19T00:00:00.000Z' },
|
|
{ id: 'e2', status: 'error', startedAt: '2026-09-18T00:00:00.000Z' },
|
|
]);
|
|
assert.ok(html.includes('data-execution-id="e1"'));
|
|
assert.ok(html.includes('data-execution-id="e2"'));
|
|
assert.ok(html.includes('bg-success'));
|
|
assert.ok(html.includes('bg-danger'));
|
|
});
|
|
|
|
test('renderExecutionList shows a message when there are no executions', () => {
|
|
const html = renderExecutionList([]);
|
|
assert.ok(html.includes('実行履歴なし'));
|
|
});
|
|
|
|
test('renderExecutionDetail lists each node result with its status', () => {
|
|
const execution = {
|
|
id: 'e1',
|
|
status: 'error',
|
|
data: {
|
|
resultData: {
|
|
runData: {
|
|
'SSH: 対象wav一覧取得': [{ error: undefined }],
|
|
'IF: 未登録ファイルあり': [{ error: { message: 'timeout' } }],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
const html = renderExecutionDetail(execution);
|
|
assert.ok(html.includes('SSH: 対象wav一覧取得'));
|
|
assert.ok(html.includes('IF: 未登録ファイルあり'));
|
|
assert.ok(html.includes('timeout'));
|
|
});
|
|
|
|
test('renderN8nPage embeds the folder tree and a container for execution details', () => {
|
|
const tree = [{ type: 'workflow', id: 'w1', name: 'ワークフローA', active: true }];
|
|
const html = renderN8nPage(tree);
|
|
assert.ok(html.includes('data-workflow-id="w1"'));
|
|
assert.ok(html.includes('id="executionList"'));
|
|
assert.ok(html.includes('id="executionDetail"'));
|
|
assert.ok(html.includes('/n8n.js'));
|
|
});
|