Scrum4Me/__tests__/actions/push.test.ts
Scrum4Me Agent 353d2dff8a ST-cmovs7ut4: actions/push.ts subscribeToPushAction + unsubscribeFromPushAction
Vervangt stub met volledige implementatie: requireUser via getSession,
demo-block, Zod-validatie, upsert met user_id-scoping en user-scoped
deleteMany. Tests (8): idempotentie, demo-block, unauthenticated, invalid input.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 21:05:50 +02:00

102 lines
3.2 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
const { mockGetSession } = vi.hoisted(() => ({
mockGetSession: vi.fn(),
}))
vi.mock('@/lib/auth', () => ({
getSession: mockGetSession,
}))
const { mockUpsert, mockDeleteMany } = vi.hoisted(() => ({
mockUpsert: vi.fn(),
mockDeleteMany: vi.fn(),
}))
vi.mock('@/lib/prisma', () => ({
prisma: {
pushSubscription: {
upsert: mockUpsert,
deleteMany: mockDeleteMany,
},
},
}))
import { subscribeToPushAction, unsubscribeFromPushAction } from '@/actions/push'
const VALID_INPUT = {
endpoint: 'https://push.example.com/subscription/abc123',
keys: { p256dh: 'aBcDeFgH', auth: 'xYzAbC' },
}
const SESSION_USER = { userId: 'user-1', isDemo: false }
const SESSION_DEMO = { userId: 'demo-1', isDemo: true }
beforeEach(() => {
vi.clearAllMocks()
mockUpsert.mockResolvedValue({})
mockDeleteMany.mockResolvedValue({ count: 1 })
})
describe('subscribeToPushAction', () => {
it('upserts subscription for authenticated user', async () => {
mockGetSession.mockResolvedValue(SESSION_USER)
await subscribeToPushAction(VALID_INPUT)
expect(mockUpsert).toHaveBeenCalledWith(
expect.objectContaining({
where: { endpoint: VALID_INPUT.endpoint },
create: expect.objectContaining({ user_id: 'user-1', endpoint: VALID_INPUT.endpoint }),
})
)
})
it('is idempotent — calling twice upserts twice without error', async () => {
mockGetSession.mockResolvedValue(SESSION_USER)
await subscribeToPushAction(VALID_INPUT)
await subscribeToPushAction(VALID_INPUT)
expect(mockUpsert).toHaveBeenCalledTimes(2)
})
it('returns without writing for demo user', async () => {
mockGetSession.mockResolvedValue(SESSION_DEMO)
await subscribeToPushAction(VALID_INPUT)
expect(mockUpsert).not.toHaveBeenCalled()
})
it('returns without writing when not authenticated', async () => {
mockGetSession.mockResolvedValue({})
await subscribeToPushAction(VALID_INPUT)
expect(mockUpsert).not.toHaveBeenCalled()
})
it('returns without writing for invalid input', async () => {
mockGetSession.mockResolvedValue(SESSION_USER)
// @ts-expect-error intentionally invalid
await subscribeToPushAction({ endpoint: 'not-a-url', keys: {} })
expect(mockUpsert).not.toHaveBeenCalled()
})
})
describe('unsubscribeFromPushAction', () => {
it('deletes subscription scoped to user_id', async () => {
mockGetSession.mockResolvedValue(SESSION_USER)
await unsubscribeFromPushAction({ endpoint: VALID_INPUT.endpoint })
expect(mockDeleteMany).toHaveBeenCalledWith({
where: { endpoint: VALID_INPUT.endpoint, user_id: 'user-1' },
})
})
it('does not touch subscriptions of other users', async () => {
mockGetSession.mockResolvedValue({ userId: 'other-user', isDemo: false })
await unsubscribeFromPushAction({ endpoint: VALID_INPUT.endpoint })
expect(mockDeleteMany).toHaveBeenCalledWith(
expect.objectContaining({ where: expect.objectContaining({ user_id: 'other-user' }) })
)
})
it('returns without writing when not authenticated', async () => {
mockGetSession.mockResolvedValue({})
await unsubscribeFromPushAction({ endpoint: VALID_INPUT.endpoint })
expect(mockDeleteMany).not.toHaveBeenCalled()
})
})