Scrum4Me/__tests__/actions/user-settings.test.ts
Madhura68 90a9ee7186 test(PBI-76): user-settings lib/action/store coverage
22 vitest cases covering merge semantics (no mutation, array
replace, nested merge), Zod schema strictness, server action
auth/demo/validation paths, and the optimistic store flow
including rollback and demo-mode skip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:42:09 +02:00

82 lines
2.9 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
vi.mock('next/headers', () => ({ cookies: vi.fn().mockResolvedValue({}) }))
vi.mock('iron-session', () => ({
getIronSession: vi.fn().mockResolvedValue({ userId: 'user-1', isDemo: false }),
}))
vi.mock('@/lib/session', () => ({
sessionOptions: { cookieName: 'test', password: 'test' },
}))
vi.mock('@/lib/prisma', () => ({
prisma: {
user: { findUnique: vi.fn() },
$transaction: vi.fn(async (fn: (tx: unknown) => Promise<unknown>) => {
return fn({
user: {
findUnique: vi.fn().mockResolvedValue({ settings: {} }),
update: vi.fn().mockResolvedValue({}),
},
})
}),
$executeRaw: vi.fn().mockResolvedValue(1),
},
}))
import { prisma } from '@/lib/prisma'
import { getIronSession } from 'iron-session'
import { updateUserSettingsAction } from '@/actions/user-settings'
const mockPrisma = prisma as unknown as {
user: { findUnique: ReturnType<typeof vi.fn> }
$transaction: ReturnType<typeof vi.fn>
$executeRaw: ReturnType<typeof vi.fn>
}
const mockGetIronSession = getIronSession as ReturnType<typeof vi.fn>
beforeEach(() => {
vi.clearAllMocks()
mockGetIronSession.mockResolvedValue({ userId: 'user-1', isDemo: false })
mockPrisma.$executeRaw.mockResolvedValue(1)
})
describe('updateUserSettingsAction', () => {
it('returns 401 when not logged in', async () => {
mockGetIronSession.mockResolvedValue({ userId: undefined, isDemo: false })
const result = await updateUserSettingsAction({})
expect(result).toEqual({ error: 'Niet ingelogd', code: 401 })
})
it('returns 403 for demo accounts', async () => {
mockGetIronSession.mockResolvedValue({ userId: 'user-1', isDemo: true })
const result = await updateUserSettingsAction({})
expect('error' in result && result.code).toBe(403)
})
it('returns 422 when patch is invalid', async () => {
const result = await updateUserSettingsAction({
views: { sprintBacklog: { filterStatus: 'NONSENSE' } },
} as never)
expect('error' in result && result.code).toBe(422)
})
it('merges with current settings and emits notify on success', async () => {
const existingFindUnique = vi.fn().mockResolvedValue({
settings: { views: { sprintBacklog: { sort: 'code' } } },
})
const update = vi.fn().mockResolvedValue({})
mockPrisma.$transaction.mockImplementationOnce(async (fn: (tx: unknown) => Promise<unknown>) => {
return fn({ user: { findUnique: existingFindUnique, update } })
})
const result = await updateUserSettingsAction({
views: { sprintBacklog: { sortDir: 'desc' } },
})
expect('success' in result && result.success).toBe(true)
expect(update).toHaveBeenCalledWith({
where: { id: 'user-1' },
data: { settings: { views: { sprintBacklog: { sort: 'code', sortDir: 'desc' } } } },
})
expect(mockPrisma.$executeRaw).toHaveBeenCalled()
})
})