Barazo AppView backend
barazo.forum
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})