Публичный GET и admin PUT/UI /admin/apps; каталог и chrome читают URL из store. Co-authored-by: Cursor <cursoragent@cursor.com>
95 lines
2.8 KiB
TypeScript
95 lines
2.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { buildApp } from '../src/app.js'
|
|
import { loadConfig } from '../src/config.js'
|
|
|
|
describe('app-switcher API', () => {
|
|
it('GET /api/v1/app-switcher is public and returns defaults', async () => {
|
|
const config = loadConfig({
|
|
...process.env,
|
|
JWT_SECRET: 'test-secret-at-least-8',
|
|
ADMIN_PASSWORD: 'admin',
|
|
DATABASE_URL: 'sqlite::memory:',
|
|
NODE_ENV: 'test',
|
|
})
|
|
const app = await buildApp({ config, databaseUrl: 'sqlite::memory:' })
|
|
const res = await app.inject({ method: 'GET', url: '/api/v1/app-switcher' })
|
|
expect(res.statusCode).toBe(200)
|
|
const body = res.json() as { menuLabel: string; apps: { id: string }[] }
|
|
expect(body.menuLabel).toBeTruthy()
|
|
expect(body.apps.map((a) => a.id).sort()).toEqual(['bgp', 'cfdm', 'vps'])
|
|
await app.close()
|
|
})
|
|
|
|
it('PUT /api/v1/admin/app-switcher requires admin and persists', async () => {
|
|
const config = loadConfig({
|
|
...process.env,
|
|
JWT_SECRET: 'test-secret-at-least-8',
|
|
ADMIN_EMAIL: 'admin@test.local',
|
|
ADMIN_PASSWORD: 'adminpass',
|
|
DATABASE_URL: 'sqlite::memory:',
|
|
NODE_ENV: 'test',
|
|
})
|
|
const app = await buildApp({ config, databaseUrl: 'sqlite::memory:' })
|
|
|
|
const denied = await app.inject({
|
|
method: 'PUT',
|
|
url: '/api/v1/admin/app-switcher',
|
|
payload: { menuLabel: 'Apps', apps: [] },
|
|
})
|
|
expect(denied.statusCode).toBe(401)
|
|
|
|
const login = await app.inject({
|
|
method: 'POST',
|
|
url: '/api/v1/auth/login',
|
|
payload: { email: 'admin@test.local', password: 'adminpass' },
|
|
})
|
|
expect(login.statusCode).toBe(200)
|
|
const token = (login.json() as { access_token: string }).access_token
|
|
|
|
const getBefore = await app.inject({
|
|
method: 'GET',
|
|
url: '/api/v1/app-switcher',
|
|
})
|
|
const before = getBefore.json() as {
|
|
menuLabel: string
|
|
apps: {
|
|
id: string
|
|
name: string
|
|
url: string
|
|
icon: string
|
|
enabled: boolean
|
|
}[]
|
|
}
|
|
|
|
const updated = {
|
|
menuLabel: 'Сервисы',
|
|
apps: before.apps.map((a) =>
|
|
a.id === 'cfdm'
|
|
? { ...a, url: 'https://cfdm.example.test', name: 'CFDM Test' }
|
|
: a,
|
|
),
|
|
}
|
|
|
|
const put = await app.inject({
|
|
method: 'PUT',
|
|
url: '/api/v1/admin/app-switcher',
|
|
headers: { authorization: `Bearer ${token}` },
|
|
payload: updated,
|
|
})
|
|
expect(put.statusCode).toBe(200)
|
|
expect(put.json()).toMatchObject({ menuLabel: 'Сервисы' })
|
|
|
|
const getAfter = await app.inject({
|
|
method: 'GET',
|
|
url: '/api/v1/app-switcher',
|
|
})
|
|
const after = getAfter.json() as typeof before
|
|
expect(after.menuLabel).toBe('Сервисы')
|
|
expect(after.apps.find((a) => a.id === 'cfdm')?.url).toBe(
|
|
'https://cfdm.example.test',
|
|
)
|
|
|
|
await app.close()
|
|
})
|
|
})
|