Barazo AppView backend barazo.forum
at main 130 lines 4.0 kB view raw
1import { describe, expect, it, vi } from 'vitest' 2 3import { topologicalSort, validateAndFilterPlugins } from '../../../../src/lib/plugins/loader.js' 4import type { PluginManifest } from '../../../../src/validation/plugin-manifest.js' 5 6function makeManifest(overrides: Partial<PluginManifest> & { name: string }): PluginManifest { 7 return { 8 displayName: overrides.name, 9 version: '1.0.0', 10 description: 'Test plugin', 11 barazoVersion: '^1.0.0', 12 source: 'community', 13 category: 'social', 14 author: { name: 'Test' }, 15 license: 'MIT', 16 permissions: { backend: [], frontend: [] }, 17 ...overrides, 18 } 19} 20 21function makeLogger() { 22 return { 23 info: vi.fn(), 24 warn: vi.fn(), 25 error: vi.fn(), 26 debug: vi.fn(), 27 trace: vi.fn(), 28 fatal: vi.fn(), 29 child: vi.fn().mockReturnThis(), 30 level: 'info', 31 } 32} 33 34describe('topologicalSort', () => { 35 it('returns plugins with no dependencies in original order', () => { 36 const a = makeManifest({ name: '@barazo/plugin-a' }) 37 const b = makeManifest({ name: '@barazo/plugin-b' }) 38 const c = makeManifest({ name: '@barazo/plugin-c' }) 39 40 const sorted = topologicalSort([a, b, c]) 41 expect(sorted.map((m) => m.name)).toEqual([ 42 '@barazo/plugin-a', 43 '@barazo/plugin-b', 44 '@barazo/plugin-c', 45 ]) 46 }) 47 48 it('orders dependencies before dependents', () => { 49 const a = makeManifest({ 50 name: '@barazo/plugin-a', 51 dependencies: ['@barazo/plugin-b'], 52 }) 53 const b = makeManifest({ name: '@barazo/plugin-b' }) 54 55 const sorted = topologicalSort([a, b]) 56 const names = sorted.map((m) => m.name) 57 expect(names.indexOf('@barazo/plugin-b')).toBeLessThan(names.indexOf('@barazo/plugin-a')) 58 }) 59 60 it('handles multi-level dependency chains (A -> B -> C)', () => { 61 const a = makeManifest({ 62 name: '@barazo/plugin-a', 63 dependencies: ['@barazo/plugin-b'], 64 }) 65 const b = makeManifest({ 66 name: '@barazo/plugin-b', 67 dependencies: ['@barazo/plugin-c'], 68 }) 69 const c = makeManifest({ name: '@barazo/plugin-c' }) 70 71 const sorted = topologicalSort([a, b, c]) 72 const names = sorted.map((m) => m.name) 73 expect(names).toEqual(['@barazo/plugin-c', '@barazo/plugin-b', '@barazo/plugin-a']) 74 }) 75 76 it('throws on circular dependencies', () => { 77 const a = makeManifest({ 78 name: '@barazo/plugin-a', 79 dependencies: ['@barazo/plugin-b'], 80 }) 81 const b = makeManifest({ 82 name: '@barazo/plugin-b', 83 dependencies: ['@barazo/plugin-a'], 84 }) 85 86 expect(() => topologicalSort([a, b])).toThrow(/circular/i) 87 }) 88}) 89 90describe('validateAndFilterPlugins', () => { 91 it('passes valid manifests through', () => { 92 const logger = makeLogger() 93 const manifests = [ 94 makeManifest({ name: '@barazo/plugin-a' }), 95 makeManifest({ name: '@barazo/plugin-b' }), 96 ] 97 98 const result = validateAndFilterPlugins(manifests, '1.0.0', logger as never) 99 expect(result).toHaveLength(2) 100 }) 101 102 it('filters out invalid manifests and logs warning', () => { 103 const logger = makeLogger() 104 const manifests = [ 105 makeManifest({ name: '@barazo/plugin-a' }), 106 { name: 'invalid-name', version: 'not-semver' }, // invalid 107 ] 108 109 const result = validateAndFilterPlugins(manifests, '1.0.0', logger as never) 110 expect(result).toHaveLength(1) 111 expect(result[0].name).toBe('@barazo/plugin-a') 112 expect(logger.warn).toHaveBeenCalled() 113 }) 114 115 it('filters out plugins with missing dependencies and logs warning', () => { 116 const logger = makeLogger() 117 const manifests = [ 118 makeManifest({ 119 name: '@barazo/plugin-a', 120 dependencies: ['@barazo/plugin-missing'], 121 }), 122 makeManifest({ name: '@barazo/plugin-b' }), 123 ] 124 125 const result = validateAndFilterPlugins(manifests, '1.0.0', logger as never) 126 expect(result).toHaveLength(1) 127 expect(result[0].name).toBe('@barazo/plugin-b') 128 expect(logger.warn).toHaveBeenCalled() 129 }) 130})