ken_nogi/NodeSrv/apps/app-portal/test/dashboard.test.js
Kenichiro NOGI ce58cb4be4 初回コミット: dev配下(NodeSrv/Pleasanter等)をGitea管理下に統合
GitHub(nextgroup2706/ken_nogi)は今後使わず自社Gitea運用に切替え。
NodeSrvは旧リポジトリの履歴を破棄しファイルのみ統合(Dokploy用サービスアカウントは
別途mygit-admin/NodeSrv.gitに履歴あり)。notepmエクスポート(12GB)とPleasanter
インストーラzip(208MB)はサイズが大きいため.gitignoreで除外。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 15:37:06 +09:00

66 lines
2.7 KiB
JavaScript

const { test } = require('node:test');
const assert = require('node:assert');
const Module = require('module');
function withMocked(modulePath, mockExports, fn) {
const resolved = require.resolve(modulePath);
const original = require.cache[resolved];
require.cache[resolved] = new Module(resolved);
require.cache[resolved].exports = mockExports;
return Promise.resolve(fn()).finally(() => {
if (original) {
require.cache[resolved] = original;
} else {
delete require.cache[resolved];
}
});
}
test('buildCards skips composes without a valid PORTAL_APP_TYPE and reports fetch errors separately', async () => {
delete require.cache[require.resolve('../src/dashboard')];
await withMocked('../src/dokployClient', {
listComposes: async () => [
{ composeId: 'c1', name: 'auth-redirect', composeStatus: 'done' },
{ composeId: 'c2', name: 'gitea', composeStatus: 'done' },
{ composeId: 'c3', name: 'broken-app', composeStatus: 'error' },
],
getComposeEnv: async (composeId) => {
if (composeId === 'c1') return { env: 'PORTAL_APP_TYPE=web\nPORTAL_APP_URL=https://x', appName: 'auth-redirect-uzt5hx' };
if (composeId === 'c2') return { env: null, appName: 'gitea-xyz' };
throw new Error('boom');
},
getContainers: async () => [{ containerId: 'abc', state: 'running' }],
}, async () => {
const { buildCards } = require('../src/dashboard');
const cards = await buildCards('env-1');
assert.strictEqual(cards.length, 2);
assert.strictEqual(cards[0].composeId, 'c1');
assert.strictEqual(cards[0].meta.appType, 'web');
assert.strictEqual(cards[0].containerState, 'running');
assert.deepStrictEqual(cards[1], { error: true, composeId: 'c3', name: 'broken-app' });
});
});
test('renderCard escapes HTML in labels and shows a "trigger" button for batch apps', () => {
delete require.cache[require.resolve('../src/dashboard')];
const { renderCard } = require('../src/dashboard');
const html = renderCard({
composeId: 'c1',
appName: 'sample',
composeStatus: 'done',
containerState: 'running',
meta: { appType: 'batch', label: '<script>bad</script>', url: 'http://sample:3000', triggerPath: '/trigger' },
});
assert.ok(!html.includes('<script>bad</script>'));
assert.ok(html.includes('&lt;script&gt;'));
assert.ok(html.includes('data-action="trigger"'));
assert.ok(!html.includes('data-action="start"'));
});
test('renderCard shows an "状態取得失敗" message for error cards', () => {
const { renderCard } = require('../src/dashboard');
const html = renderCard({ error: true, composeId: 'c3', name: 'broken-app' });
assert.ok(html.includes('状態取得失敗'));
assert.ok(html.includes('broken-app'));
});