|
@@ -115,6 +115,139 @@ describe('8800 BFF', () => {
|
|
|
runtimes.push(restarted)
|
|
runtimes.push(restarted)
|
|
|
expect(restarted.csrf.verify(cookies, bootstrap.body.csrfToken)).toBe(true)
|
|
expect(restarted.csrf.verify(cookies, bootstrap.body.csrfToken)).toBe(true)
|
|
|
})
|
|
})
|
|
|
|
|
+
|
|
|
|
|
+ it('installs a fresh Yaoyao plugin through authenticated Hermes and restarts supervised 9119', async () => {
|
|
|
|
|
+ const records: RecordedRequest[] = []
|
|
|
|
|
+ const baseGateway = fakeGateway(records)
|
|
|
|
|
+ const fetchImpl = (async (input: string | URL | Request, init?: RequestInit) => {
|
|
|
|
|
+ const url = new URL(input instanceof Request ? input.url : String(input))
|
|
|
|
|
+ if (url.pathname === '/api/dashboard/plugins') return jsonResponse([])
|
|
|
|
|
+ if (url.pathname === '/api/dashboard/agent-plugins/install') {
|
|
|
|
|
+ const rawBody = typeof init?.body === 'string' ? init.body : '{}'
|
|
|
|
|
+ records.push({
|
|
|
|
|
+ path: url.pathname,
|
|
|
|
|
+ method: init?.method ?? 'GET',
|
|
|
|
|
+ search: url.searchParams,
|
|
|
|
|
+ headers: new Headers(init?.headers),
|
|
|
|
|
+ body: JSON.parse(rawBody),
|
|
|
|
|
+ })
|
|
|
|
|
+ return jsonResponse({ ok: true, plugin_name: 'yaoyao', enabled: true })
|
|
|
|
|
+ }
|
|
|
|
|
+ return baseGateway(input, init)
|
|
|
|
|
+ }) as typeof fetch
|
|
|
|
|
+ let restarts = 0
|
|
|
|
|
+ const runtime = createApplication({
|
|
|
|
|
+ config: makeConfig(),
|
|
|
|
|
+ fetchImpl,
|
|
|
|
|
+ restartDashboard: async () => { restarts += 1 },
|
|
|
|
|
+ })
|
|
|
|
|
+ runtimes.push(runtime)
|
|
|
|
|
+ const bootstrap = await request(runtime.app.callback())
|
|
|
|
|
+ .get('/api/app/bootstrap').set('Host', '127.0.0.1:8800').expect(200)
|
|
|
|
|
+
|
|
|
|
|
+ const response = await request(runtime.app.callback())
|
|
|
|
|
+ .post('/api/app/plugins/yaoyao/install')
|
|
|
|
|
+ .set('Host', '127.0.0.1:8800')
|
|
|
|
|
+ .set('Origin', 'http://127.0.0.1:8800')
|
|
|
|
|
+ .set('Cookie', cookieHeader(bootstrap))
|
|
|
|
|
+ .set('X-CSRF-Token', bootstrap.body.csrfToken)
|
|
|
|
|
+ .send({ force: false })
|
|
|
|
|
+ .expect(200)
|
|
|
|
|
+
|
|
|
|
|
+ const install = records.find((entry) => entry.path === '/api/dashboard/agent-plugins/install')
|
|
|
|
|
+ expect(install?.body).toEqual({
|
|
|
|
|
+ identifier: 'https://git.samien.cn/samien/hermes-yaoyao.git#hermes-plugins/yaoyao',
|
|
|
|
|
+ force: false,
|
|
|
|
|
+ enable: true,
|
|
|
|
|
+ })
|
|
|
|
|
+ expect(restarts).toBe(1)
|
|
|
|
|
+ expect(response.body).toMatchObject({
|
|
|
|
|
+ ok: true,
|
|
|
|
|
+ plugin_name: 'yaoyao',
|
|
|
|
|
+ restarted: true,
|
|
|
|
|
+ restartRequired: false,
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('refuses to replace an existing Yaoyao before durable storage is ready', async () => {
|
|
|
|
|
+ const records: RecordedRequest[] = []
|
|
|
|
|
+ const baseGateway = fakeGateway(records)
|
|
|
|
|
+ const fetchImpl = (async (input: string | URL | Request, init?: RequestInit) => {
|
|
|
|
|
+ const url = new URL(input instanceof Request ? input.url : String(input))
|
|
|
|
|
+ if (url.pathname === '/api/dashboard/plugins') {
|
|
|
|
|
+ return jsonResponse([{ name: 'yaoyao', version: '1.7.1' }])
|
|
|
|
|
+ }
|
|
|
|
|
+ if (url.pathname === '/api/plugins/yaoyao/maintenance/storage') {
|
|
|
|
|
+ return jsonResponse({ detail: 'Not Found' }, { status: 404 })
|
|
|
|
|
+ }
|
|
|
|
|
+ return baseGateway(input, init)
|
|
|
|
|
+ }) as typeof fetch
|
|
|
|
|
+ const runtime = createApplication({ config: makeConfig(), fetchImpl })
|
|
|
|
|
+ runtimes.push(runtime)
|
|
|
|
|
+ const bootstrap = await request(runtime.app.callback())
|
|
|
|
|
+ .get('/api/app/bootstrap').set('Host', '127.0.0.1:8800').expect(200)
|
|
|
|
|
+
|
|
|
|
|
+ const response = await request(runtime.app.callback())
|
|
|
|
|
+ .post('/api/app/plugins/yaoyao/install')
|
|
|
|
|
+ .set('Host', '127.0.0.1:8800')
|
|
|
|
|
+ .set('Origin', 'http://127.0.0.1:8800')
|
|
|
|
|
+ .set('Cookie', cookieHeader(bootstrap))
|
|
|
|
|
+ .set('X-CSRF-Token', bootstrap.body.csrfToken)
|
|
|
|
|
+ .send({ force: true })
|
|
|
|
|
+ .expect(409)
|
|
|
|
|
+
|
|
|
|
|
+ expect(response.body.code).toBe('yaoyao_storage_migration_required')
|
|
|
|
|
+ expect(records.some((entry) => entry.path === '/api/dashboard/agent-plugins/install')).toBe(false)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('upgrades an existing Yaoyao only after storage readiness and explicit force', async () => {
|
|
|
|
|
+ const records: RecordedRequest[] = []
|
|
|
|
|
+ const baseGateway = fakeGateway(records)
|
|
|
|
|
+ const fetchImpl = (async (input: string | URL | Request, init?: RequestInit) => {
|
|
|
|
|
+ const url = new URL(input instanceof Request ? input.url : String(input))
|
|
|
|
|
+ if (url.pathname === '/api/dashboard/plugins') {
|
|
|
|
|
+ return jsonResponse([{ name: 'yaoyao', version: '1.7.1' }])
|
|
|
|
|
+ }
|
|
|
|
|
+ if (url.pathname === '/api/plugins/yaoyao/maintenance/storage') {
|
|
|
|
|
+ return jsonResponse({ ready: true, profiles: [{ profile: 'default', ready: true }] })
|
|
|
|
|
+ }
|
|
|
|
|
+ if (url.pathname === '/api/dashboard/agent-plugins/install') {
|
|
|
|
|
+ const rawBody = typeof init?.body === 'string' ? init.body : '{}'
|
|
|
|
|
+ records.push({
|
|
|
|
|
+ path: url.pathname,
|
|
|
|
|
+ method: init?.method ?? 'GET',
|
|
|
|
|
+ search: url.searchParams,
|
|
|
|
|
+ headers: new Headers(init?.headers),
|
|
|
|
|
+ body: JSON.parse(rawBody),
|
|
|
|
|
+ })
|
|
|
|
|
+ return jsonResponse({ ok: true, plugin_name: 'yaoyao', enabled: true })
|
|
|
|
|
+ }
|
|
|
|
|
+ return baseGateway(input, init)
|
|
|
|
|
+ }) as typeof fetch
|
|
|
|
|
+ const runtime = createApplication({ config: makeConfig(), fetchImpl })
|
|
|
|
|
+ runtimes.push(runtime)
|
|
|
|
|
+ const bootstrap = await request(runtime.app.callback())
|
|
|
|
|
+ .get('/api/app/bootstrap').set('Host', '127.0.0.1:8800').expect(200)
|
|
|
|
|
+ const common = request(runtime.app.callback())
|
|
|
|
|
+ .post('/api/app/plugins/yaoyao/install')
|
|
|
|
|
+ .set('Host', '127.0.0.1:8800')
|
|
|
|
|
+ .set('Origin', 'http://127.0.0.1:8800')
|
|
|
|
|
+ .set('Cookie', cookieHeader(bootstrap))
|
|
|
|
|
+ .set('X-CSRF-Token', bootstrap.body.csrfToken)
|
|
|
|
|
+
|
|
|
|
|
+ await common.send({ force: false }).expect(409)
|
|
|
|
|
+ await request(runtime.app.callback())
|
|
|
|
|
+ .post('/api/app/plugins/yaoyao/install')
|
|
|
|
|
+ .set('Host', '127.0.0.1:8800')
|
|
|
|
|
+ .set('Origin', 'http://127.0.0.1:8800')
|
|
|
|
|
+ .set('Cookie', cookieHeader(bootstrap))
|
|
|
|
|
+ .set('X-CSRF-Token', bootstrap.body.csrfToken)
|
|
|
|
|
+ .send({ force: true })
|
|
|
|
|
+ .expect(200)
|
|
|
|
|
+
|
|
|
|
|
+ const install = records.find((entry) => entry.path === '/api/dashboard/agent-plugins/install')
|
|
|
|
|
+ expect(install?.body).toMatchObject({ force: true, enable: true })
|
|
|
|
|
+ })
|
|
|
it('serves authenticated historical media only from the configured root', async () => {
|
|
it('serves authenticated historical media only from the configured root', async () => {
|
|
|
const config = makeConfig()
|
|
const config = makeConfig()
|
|
|
writeFileSync(join(config.mediaRoot, '报告.txt'), '历史文件内容')
|
|
writeFileSync(join(config.mediaRoot, '报告.txt'), '历史文件内容')
|