fix(web): error/404 экраны, сплит бандла и bulk-мутации
- RouteErrorComponent/RouteNotFoundComponent на root-маршруте (RU-копирайт, retry + переход на главную; redirect в портал не мигает ошибкой) - settings: форма инициализируется один раз — фоновый refetch больше не затирает ввод пользователя - code splitting: autoCodeSplitting роутов + manualChunks (react/router/query/ charts/dnd); вход ~507KB вместо единого чанка 1.57MB, recharts (330KB) грузится лениво; версия recharts в packages/ui выровнена с apps/web (3.8.0) - bulk-эндпоинты: POST /agents/approve-bulk и PUT /policy-sets/:id/agents — назначение набора агентам одним запросом вместо N×(GET+PUT) - оптимистичные обновления с rollback: approve, approve-bulk, удаление агента, переключение набора - тесты bulk-операций (45 passed)
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
import { describe, it, expect, afterAll } from 'vitest'
|
||||
import { buildApp } from '../app.js'
|
||||
import type { AppConfig } from '../config.js'
|
||||
|
||||
const testConfig: AppConfig = {
|
||||
databaseUrl: 'sqlite::memory:',
|
||||
jwtSecret: 'test',
|
||||
jwtTtlHours: 24,
|
||||
serverPort: 8080,
|
||||
staticDir: null,
|
||||
logLevel: 'error',
|
||||
authRequired: false,
|
||||
authIssuer: 'https://auth.test',
|
||||
authPortalUrl: 'http://localhost:5175',
|
||||
publicBaseUrl: 'https://fw.example.com',
|
||||
enrollSeed: 'test-seed',
|
||||
corsOrigins: [],
|
||||
secretKey: null,
|
||||
}
|
||||
|
||||
async function createInvitedAgent(
|
||||
app: Awaited<ReturnType<typeof buildApp>>,
|
||||
name: string,
|
||||
): Promise<string> {
|
||||
const created = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/install-links',
|
||||
payload: { name, platform: 'linux' },
|
||||
})
|
||||
expect(created.statusCode).toBe(201)
|
||||
return (created.json() as { agent_id: string }).agent_id
|
||||
}
|
||||
|
||||
describe('bulk agent operations', () => {
|
||||
const appPromise = buildApp({ memory: true, config: testConfig })
|
||||
|
||||
afterAll(async () => {
|
||||
const app = await appPromise
|
||||
await app.close()
|
||||
})
|
||||
|
||||
it('approve-bulk approves invited agents in one request', async () => {
|
||||
const app = await appPromise
|
||||
await app.ready()
|
||||
|
||||
const a1 = await createInvitedAgent(app, 'bulk-01')
|
||||
const a2 = await createInvitedAgent(app, 'bulk-02')
|
||||
const a3 = await createInvitedAgent(app, 'bulk-03')
|
||||
|
||||
const bulk = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/agents/approve-bulk',
|
||||
payload: { agent_ids: [a1, a2, a3] },
|
||||
})
|
||||
expect(bulk.statusCode).toBe(200)
|
||||
const body = bulk.json() as { items: { id: string; status: string }[] }
|
||||
expect(body.items.map((i) => i.id).sort()).toEqual([a1, a2, a3].sort())
|
||||
expect(body.items.every((i) => i.status === 'approved')).toBe(true)
|
||||
|
||||
// shared default set assigned on approve
|
||||
for (const id of [a1, a2, a3]) {
|
||||
const sets = await app.inject({
|
||||
method: 'GET',
|
||||
url: `/api/v1/agents/${id}/policy-sets`,
|
||||
})
|
||||
const items = (sets.json() as { items: { set_id: string }[] }).items
|
||||
expect(items.some((s) => s.set_id === 'set-shared-default')).toBe(true)
|
||||
}
|
||||
|
||||
// repeated bulk is a no-op (already approved)
|
||||
const again = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/agents/approve-bulk',
|
||||
payload: { agent_ids: [a1] },
|
||||
})
|
||||
expect(again.statusCode).toBe(200)
|
||||
expect((again.json() as { items: unknown[] }).items).toEqual([])
|
||||
})
|
||||
|
||||
it('PUT /policy-sets/:id/agents replaces assignment of the set', async () => {
|
||||
const app = await appPromise
|
||||
await app.ready()
|
||||
|
||||
const a1 = await createInvitedAgent(app, 'assign-01')
|
||||
const a2 = await createInvitedAgent(app, 'assign-02')
|
||||
await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/agents/approve-bulk',
|
||||
payload: { agent_ids: [a1, a2] },
|
||||
})
|
||||
|
||||
const created = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/v1/policy-sets',
|
||||
payload: { name: 'bulk-assign-set' },
|
||||
})
|
||||
const setId = (created.json() as { id: string }).id
|
||||
|
||||
// assign both
|
||||
const put = await app.inject({
|
||||
method: 'PUT',
|
||||
url: `/api/v1/policy-sets/${setId}/agents`,
|
||||
payload: { agent_ids: [a1, a2] },
|
||||
})
|
||||
expect(put.statusCode).toBe(200)
|
||||
expect((put.json() as { added: string[] }).added.sort()).toEqual(
|
||||
[a1, a2].sort(),
|
||||
)
|
||||
|
||||
// a1 keeps set when a2 removed; shared default preserved for both
|
||||
const drop = await app.inject({
|
||||
method: 'PUT',
|
||||
url: `/api/v1/policy-sets/${setId}/agents`,
|
||||
payload: { agent_ids: [a1] },
|
||||
})
|
||||
expect(drop.statusCode).toBe(200)
|
||||
const dropBody = drop.json() as {
|
||||
agent_ids: string[]
|
||||
removed: string[]
|
||||
}
|
||||
expect(dropBody.agent_ids).toEqual([a1])
|
||||
expect(dropBody.removed).toEqual([a2])
|
||||
|
||||
const setsA1 = (
|
||||
(await app.inject({ method: 'GET', url: `/api/v1/agents/${a1}/policy-sets` }))
|
||||
.json() as { items: { set_id: string }[] }
|
||||
).items.map((s) => s.set_id)
|
||||
const setsA2 = (
|
||||
(await app.inject({ method: 'GET', url: `/api/v1/agents/${a2}/policy-sets` }))
|
||||
.json() as { items: { set_id: string }[] }
|
||||
).items.map((s) => s.set_id)
|
||||
expect(setsA1).toContain(setId)
|
||||
expect(setsA2).not.toContain(setId)
|
||||
expect(setsA2).toContain('set-shared-default')
|
||||
|
||||
// empty array clears the whole assignment
|
||||
const clear = await app.inject({
|
||||
method: 'PUT',
|
||||
url: `/api/v1/policy-sets/${setId}/agents`,
|
||||
payload: { agent_ids: [] },
|
||||
})
|
||||
expect(clear.statusCode).toBe(200)
|
||||
expect((clear.json() as { agent_ids: string[] }).agent_ids).toEqual([])
|
||||
|
||||
// unknown agent → 404, and nothing changed
|
||||
const bad = await app.inject({
|
||||
method: 'PUT',
|
||||
url: `/api/v1/policy-sets/${setId}/agents`,
|
||||
payload: { agent_ids: ['no-such-agent'] },
|
||||
})
|
||||
expect(bad.statusCode).toBe(404)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user