import { describe, expect, it } from 'bun:test'; import { manifestProblems, mountPrefix, type PluginManifest } from './manifest'; const valid = (over: Partial = {}): PluginManifest => ({ publisher: 'officerdev', version: '1.0.0', platform: '>=1.0.0 <2.0.0', label: 'Offscale', summary: 'Your tailnet', icon: 'Network', color: '#818cf8', permissions: [{ key: 'offscale', label: 'Offscale', description: 'The tailnet' }], ...over, }); describe('mountPrefix', () => { it('puts first-party plugins at the root', () => { expect(mountPrefix({ appName: 'offscale', manifest: { publisher: 'officerdev' } })).toBe('/offscale'); }); it('puts everyone else under /p//', () => { expect(mountPrefix({ appName: 'notes', manifest: { publisher: 'alice' } })).toBe('/p/alice/notes'); }); // The property the segment exists for: a third party cannot reach a core route's namespace, whatever // they call their plugin. Without it, publishing `notes` at /api/notes would mean the platform could // never add /api/notes itself. it('cannot shadow a core route, whatever the plugin is called', () => { for (const core of ['chat', 'users', 'terminal', 'auth', 'files']) { expect(mountPrefix({ appName: core, manifest: { publisher: 'alice' } })).toBe(`/p/alice/${core}`); } }); }); describe('manifestProblems', () => { it('accepts a good manifest', () => { expect(manifestProblems('offscale', valid())).toEqual([]); }); it('rejects a missing manifest with a reason rather than a crash', () => { expect(manifestProblems('offscale', null)).toHaveLength(1); }); it('reports every problem at once, not the first', () => { // An install fixed one field per attempt is an install nobody finishes. const problems = manifestProblems('offscale', { publisher: 'officerdev' } as Partial); expect(problems.length).toBeGreaterThan(3); }); // The app name is a URL segment, a SQL identifier prefix and a directory name simultaneously. Anything // that is not safe in all three has to be refused at the door. it.each([ ['Offscale', 'uppercase'], ['1offscale', 'leading digit'], ['off scale', 'space'], ['off_scale', 'underscore'], ['off/scale', 'slash'], ['../escape', 'traversal'], ['', 'empty'], ])('refuses the directory name %p (%s)', (appName) => { expect(manifestProblems(appName, valid()).length).toBeGreaterThan(0); }); it('refuses a publisher that is not a safe path segment', () => { expect(manifestProblems('notes', valid({ publisher: '../evil' })).length).toBeGreaterThan(0); }); it('requires permissions to be an array, so [] is how a plugin says it gates nothing', () => { expect(manifestProblems('notes', valid({ permissions: [] }))).toEqual([]); expect(manifestProblems('notes', valid({ permissions: undefined })).length).toBeGreaterThan(0); }); it('names the permission that is malformed, by index', () => { const problems = manifestProblems('notes', valid({ permissions: [{ label: 'x' }] as never })); expect(problems.some((p) => p.includes('permissions[0].key'))).toBe(true); }); });