Files
zpan/server/routes/system.integration.test.ts
T
Jasper Van ccc4266b5f Fix admin settings persistence and batch user operations (#358)
* fix: persist admin settings and batch user operations

Agent-Profile: https://agent-kanban.dev/agents/a318237412dd8b98

* test: cover admin quota edge cases

Agent-Profile: https://agent-kanban.dev/agents/a318237412dd8b98

* fix: avoid duplicate batch quota toast

Agent-Profile: https://agent-kanban.dev/agents/a318237412dd8b98
2026-05-04 17:20:26 -04:00

97 lines
3.7 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { adminHeaders, createTestApp } from '../test/setup.js'
async function putOption(
app: Awaited<ReturnType<typeof createTestApp>>['app'],
headers: Record<string, string>,
key: string,
body: Record<string, unknown>,
) {
return app.request(`/api/system/options/${key}`, {
method: 'PUT',
headers: { ...headers, 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
}
describe('System API — options CRUD', () => {
it('GET unknown key returns 404', async () => {
const { app } = await createTestApp()
const res = await app.request('/api/system/options/site_name')
expect(res.status).toBe(404)
})
it('full admin CRUD lifecycle with public/private visibility', async () => {
const { app } = await createTestApp()
const admin = await adminHeaders(app)
// Create public option
const put = await putOption(app, admin, 'site_name', { value: 'ZPan', public: true })
expect(put.status).toBe(201)
expect(await put.json()).toEqual({ key: 'site_name', value: 'ZPan', public: true })
// Anonymous can read public option
const anonGet = await app.request('/api/system/options/site_name')
expect(anonGet.status).toBe(200)
expect(await anonGet.json()).toEqual({ key: 'site_name', value: 'ZPan', public: true })
// Update preserves public flag when omitted
const put2 = await putOption(app, admin, 'site_name', { value: 'v2' })
expect(put2.status).toBe(200)
expect(await put2.json()).toEqual({ key: 'site_name', value: 'v2', public: true })
// Private option hidden from anonymous
await putOption(app, admin, 'smtp_password', { value: 'secret', public: false })
const anonPrivate = await app.request('/api/system/options/smtp_password')
expect(anonPrivate.status).toBe(403)
// Admin can read private option
const adminPrivate = await app.request('/api/system/options/smtp_password', { headers: admin })
expect(adminPrivate.status).toBe(200)
expect(await adminPrivate.json()).toEqual({ key: 'smtp_password', value: 'secret', public: false })
// List: anon sees only public, admin sees all
const anonList = await app.request('/api/system/options')
const anonBody = (await anonList.json()) as { items: { key: string }[]; total: number }
expect(anonBody.total).toBe(1)
expect(anonBody.items[0].key).toBe('site_name')
const adminList = await app.request('/api/system/options', { headers: admin })
const adminBody = (await adminList.json()) as { total: number }
expect(adminBody.total).toBe(2)
// Non-string value rejected
const bad = await putOption(app, admin, 'site_name', { value: 123 })
expect(bad.status).toBe(400)
// Delete
const del = await app.request('/api/system/options/site_name', { method: 'DELETE', headers: admin })
expect(del.status).toBe(200)
const afterDel = await app.request('/api/system/options/site_name')
expect(afterDel.status).toBe(404)
})
it('unauthenticated mutations are rejected', async () => {
const { app } = await createTestApp()
const res = await app.request('/api/system/options/site_name', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ value: 'ZPan' }),
})
expect(res.status).toBe(401)
const del = await app.request('/api/system/options/site_name', { method: 'DELETE' })
expect(del.status).toBe(401)
})
it('rejects invalid default organization quota values', async () => {
const { app } = await createTestApp()
const admin = await adminHeaders(app)
for (const value of ['0', '-1', '1.5', 'abc']) {
const res = await putOption(app, admin, 'default_org_quota', { value })
expect(res.status).toBe(400)
}
})
})