Something went wrong. Try again.
ATProto-based AI Assistant Chat interface.
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'import { tmpdir } from 'node:os'import { join } from 'node:path'import { fileURLToPath } from 'node:url'import { describe, expect, it } from 'vitest'import { parse } from 'yaml'import { loadConfig } from '../../src/config.ts'import { importPlugins } from '../../src/plugins/import.ts'
const fixtures = fileURLToPath(new URL('../fixtures/plugins', import.meta.url))const repoRoot = fileURLToPath(new URL('../../../..', import.meta.url))
describe('importPlugins', () => { it('loads a local plugin by path, validating and defaulting its options', async () => { const plugins = await importPlugins( [{ package: './good' }, { package: './good', options: { greeting: 'hi' } }], fixtures, ) expect(plugins.map((plugin) => plugin.id)).toEqual(['good-hello', 'good-hi']) })
it('stops with an error naming the package and option when options fail the schema', async () => { await expect( importPlugins([{ package: './good', options: { count: 'many' } }], fixtures), ).rejects.toThrow(/"\.\/good".*count/) await expect( importPlugins([{ package: './good', options: { typo: true } }], fixtures), ).rejects.toThrow(/"\.\/good"/) })
it('stops with an error naming a package that cannot be found, and why', async () => { await expect(importPlugins([{ package: '@scn-chat/plugin-nope' }], fixtures)).rejects.toThrow( /Cannot load plugin package "@scn-chat\/plugin-nope".*Cannot find/, ) })
it('reports a broken package.json in a local plugin as a parse error', async () => { const dir = mkdtempSync(join(tmpdir(), 'scn-plugin-')) writeFileSync(join(dir, 'package.json'), '{ not json') await expect(importPlugins([{ package: dir }], fixtures)).rejects.toThrow(/JSON/) rmSync(dir, { recursive: true, force: true }) })
it('stops when a package has no factory', async () => { await expect(importPlugins([{ package: './noexport' }], fixtures)).rejects.toThrow( /no default export factory/, ) })
it('resolves npm packages from the config directory', async () => { const [plugin] = await importPlugins([{ package: '@scn-chat/plugin-titles' }], repoRoot) expect(plugin?.id).toBe('titles') })})
describe('config.example.yml', () => { it('loads with every shipped plugin, with no secrets set', async () => { const file = parse(readFileSync(`${repoRoot}/config.example.yml`, 'utf8')) const config = loadConfig({ env: { SECRET_KEY: Buffer.alloc(32, 1).toString('base64') }, file, configPath: `${repoRoot}/config.example.yml`, }) const plugins = await importPlugins(config.plugins, config.configDir) expect(plugins.map((plugin) => plugin.id)).toEqual([ 'openai-compatible-scn', 'openai-compatible-cocore', 'anthropic', 'openai', 'google', 'openai-compatible-custom', 'pdf-text', 'titles', ]) })})