import { test } from 'node:test'; import assert from 'node:assert/strict'; import { Hono } from 'hono'; import { createManager } from '../src/plugin-manager.js'; // Tiny sub-app that answers GET / with a fixed text. function mk(text) { const a = new Hono(); a.get('/', (c) => c.text(text)); return a; } function fixture() { const state = { booted: false, published: null, previewed: null }; const demo = { name: 'demo', routes: [ // sub-app routes { path: '/demo-pub', app: mk('pub'), public: true }, { path: '/demo-priv', app: mk('priv') }, { path: '/demo-adm', app: mk('adm'), admin: true }, // handler routes (method + handler) { method: 'get', path: '/h-pub', handler: (c) => c.text('hpub'), public: true }, { method: 'post', path: '/h-priv', handler: (c) => c.text('hpriv') }, // per-route middleware (e.g. a rate limit) { method: 'post', path: '/h-limited', public: true, use: [async (c, next) => (c.req.header('x-pass') ? next() : c.json({ e: 'limited' }, 429))], handler: (c) => c.text('ok'), }, ], onBoot: async () => { state.booted = true; }, onPublish: async (_ctx, p) => { state.published = p.path; }, onPreview: async (_ctx, p) => { state.previewed = p.path; }, stats: async () => ({ widgets: 3 }), migrations: ['001_demo.sql'], }; return { demo, state }; } // Build the same pipeline as index.js: public → requireAuth → private(+admin). function buildApp(m) { const app = new Hono(); m.mountPublic(app); app.use('/api/*', async (c, next) => (c.req.header('x-auth') ? next() : c.json({ error: 'no auth' }, 401))); const requireAdmin = async (c, next) => (c.req.header('x-admin') ? next() : c.json({ error: 'no admin' }, 403)); m.mountPrivate(app, '/api', requireAdmin); return app; } test('manager: names / has', () => { const { demo } = fixture(); const m = createManager([demo]); assert.deepEqual(m.names(), ['demo']); assert.equal(m.has('demo'), true); assert.equal(m.has('nope'), false); }); test('manager: public routes reachable WITHOUT auth (sub-app + handler)', async () => { const app = buildApp(createManager([fixture().demo])); const sub = await app.request('/api/demo-pub'); assert.equal(sub.status, 200); assert.equal(await sub.text(), 'pub'); const h = await app.request('/api/h-pub'); assert.equal(h.status, 200); assert.equal(await h.text(), 'hpub'); }); test('manager: private routes require auth (sub-app + handler)', async () => { const app = buildApp(createManager([fixture().demo])); assert.equal((await app.request('/api/demo-priv')).status, 401); assert.equal((await app.request('/api/h-priv', { method: 'POST' })).status, 401); const ok = await app.request('/api/h-priv', { method: 'POST', headers: { 'x-auth': '1' } }); assert.equal(ok.status, 200); assert.equal(await ok.text(), 'hpriv'); }); test('manager: admin route guarded by requireAdmin', async () => { const app = buildApp(createManager([fixture().demo])); assert.equal((await app.request('/api/demo-adm', { headers: { 'x-auth': '1' } })).status, 403); const ok = await app.request('/api/demo-adm', { headers: { 'x-auth': '1', 'x-admin': '1' } }); assert.equal(ok.status, 200); assert.equal(await ok.text(), 'adm'); }); test('manager: per-route use middleware runs (rate-limit style)', async () => { const app = buildApp(createManager([fixture().demo])); assert.equal((await app.request('/api/h-limited', { method: 'POST' })).status, 429); const ok = await app.request('/api/h-limited', { method: 'POST', headers: { 'x-pass': '1' } }); assert.equal(ok.status, 200); assert.equal(await ok.text(), 'ok'); }); test('manager: lifecycle hooks run', async () => { const { demo, state } = fixture(); const m = createManager([demo]); await m.runBoot({}); await m.runPublish({}, { path: 'a.md' }); await m.runPreview({}, { path: 'b.md' }); assert.equal(state.booted, true); assert.equal(state.published, 'a.md'); assert.equal(state.previewed, 'b.md'); }); test('manager: stats merged under plugin name', async () => { const m = createManager([fixture().demo]); assert.deepEqual(await m.collectStats({}), { demo: { widgets: 3 } }); }); test('manager: declared migrations surfaced', () => { const m = createManager([fixture().demo]); assert.deepEqual(m.migrations(), [{ plugin: 'demo', file: '001_demo.sql', url: null }]); }); test('manager: empty plugin set is a no-op', async () => { const m = createManager([]); assert.deepEqual(m.names(), []); assert.deepEqual(await m.collectStats({}), {}); assert.deepEqual(m.migrations(), []); const app = buildApp(m); assert.equal((await app.request('/api/anything', { headers: { 'x-auth': '1' } })).status, 404); });