feat(ST-qfpqpxzy): DB schema + settings-UI voor min_quota_pct worker-drempel
- User.min_quota_pct Int @default(20) + ClaudeWorker.last_quota_pct/last_quota_check_at - Migratie add_worker_quota_gate - lib/schemas/user.ts: minQuotaPctSchema (int, 1-100) - actions/settings.ts: updateMinQuotaPctAction met auth/demo/zod-guard - MinQuotaEditor component met numeric input en DemoTooltip - Settings-pagina: Worker-instellingen sectie Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
78543ee796
commit
3a73b4f1c9
7 changed files with 190 additions and 4 deletions
72
__tests__/actions/settings.test.ts
Normal file
72
__tests__/actions/settings.test.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
|
||||
const { mockUserUpdate, mockGetIronSession } = vi.hoisted(() => ({
|
||||
mockUserUpdate: vi.fn(),
|
||||
mockGetIronSession: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('next/cache', () => ({ revalidatePath: vi.fn() }))
|
||||
vi.mock('next/headers', () => ({ cookies: vi.fn().mockResolvedValue({}) }))
|
||||
vi.mock('iron-session', () => ({ getIronSession: mockGetIronSession }))
|
||||
vi.mock('@/lib/session', () => ({ sessionOptions: { cookieName: 'test', password: 'test' } }))
|
||||
vi.mock('@/lib/prisma', () => ({
|
||||
prisma: { user: { update: mockUserUpdate } },
|
||||
}))
|
||||
|
||||
import { updateMinQuotaPctAction } from '@/actions/settings'
|
||||
|
||||
const SESSION_USER = { userId: 'user-1', isDemo: false }
|
||||
const SESSION_DEMO = { userId: 'demo-1', isDemo: true }
|
||||
const SESSION_UNAUTH = { userId: undefined, isDemo: false }
|
||||
|
||||
describe('updateMinQuotaPctAction', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockUserUpdate.mockResolvedValue({})
|
||||
})
|
||||
|
||||
it('returns error when not authenticated', async () => {
|
||||
mockGetIronSession.mockResolvedValue(SESSION_UNAUTH)
|
||||
const result = await updateMinQuotaPctAction(20)
|
||||
expect(result).toMatchObject({ error: expect.any(String) })
|
||||
expect(mockUserUpdate).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('returns 403 error for demo session', async () => {
|
||||
mockGetIronSession.mockResolvedValue(SESSION_DEMO)
|
||||
const result = await updateMinQuotaPctAction(20)
|
||||
expect(result).toMatchObject({ status: 403 })
|
||||
expect(mockUserUpdate).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('returns 422 error when value is 0 (below min)', async () => {
|
||||
mockGetIronSession.mockResolvedValue(SESSION_USER)
|
||||
const result = await updateMinQuotaPctAction(0)
|
||||
expect(result).toMatchObject({ status: 422 })
|
||||
expect(mockUserUpdate).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('returns 422 error when value is 101 (above max)', async () => {
|
||||
mockGetIronSession.mockResolvedValue(SESSION_USER)
|
||||
const result = await updateMinQuotaPctAction(101)
|
||||
expect(result).toMatchObject({ status: 422 })
|
||||
expect(mockUserUpdate).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('saves valid value and returns success', async () => {
|
||||
mockGetIronSession.mockResolvedValue(SESSION_USER)
|
||||
const result = await updateMinQuotaPctAction(35)
|
||||
expect(result).toEqual({ success: true })
|
||||
expect(mockUserUpdate).toHaveBeenCalledWith({
|
||||
where: { id: 'user-1' },
|
||||
data: { min_quota_pct: 35 },
|
||||
})
|
||||
})
|
||||
|
||||
it('accepts boundary values 1 and 100', async () => {
|
||||
mockGetIronSession.mockResolvedValue(SESSION_USER)
|
||||
await updateMinQuotaPctAction(1)
|
||||
await updateMinQuotaPctAction(100)
|
||||
expect(mockUserUpdate).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue