Shumiland/tests/unit/lib/ezy.test.ts
Vadym Samoilenko cca4ea1d55
Some checks are pending
CI / Type Check (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Unit Tests (push) Waiting to run
Deploy / Build & Push Image (push) Waiting to run
Deploy / Deploy to VPS (push) Blocked by required conditions
feat: implement full frontend — all sections, components, Figma Code Connect
- All 8 home page sections: Hero, Locations slider, WhyParents accordion,
  Birthday pricing cards, Video, Gallery, Reviews slider, News
- UI components: NavLink, BtnPrimary, BtnGradient, BtnDetails, AccordionItem
- Layout: sticky Header (NavLink + BtnPrimary), Footer with logo
- Figma Code Connect: 5 components published (.figma.tsx + figma.config.json)
- Public assets: all Figma images and SVGs exported
- Pages: /kvytky, /lokatsii, /blog, /dni-narodzhennia, /grupovi-vidviduvannia
- Tests: Vitest unit/api suites, Playwright e2e screenshots
- Payload CMS: blocks, collections, seed data updates
- Hero negative-margin to extend behind sticky header
- Custom Tailwind breakpoints: lg=1440px, xl=1920px
- Fix ESLint config: drop FlatCompat, use eslint-config-next flat export
- Add tsconfig.tsbuildinfo, test-results/, agentdb.rvf* to .gitignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 16:40:56 +01:00

158 lines
5.4 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
const mockFetch = vi.fn()
vi.stubGlobal('fetch', mockFetch)
function okResponse(data: unknown): Response {
return {
ok: true,
status: 200,
json: async () => data,
} as unknown as Response
}
function errorResponse(status: number): Response {
return { ok: false, status, json: async () => ({}) } as unknown as Response
}
// Reset module cache between tests so tariffsCache is cleared
let getTariffs: () => Promise<unknown>
let createPayment: (
email: string,
items: Array<{ tariff: string; count: string }>
) => Promise<{ url: string }>
beforeEach(async () => {
vi.resetModules()
mockFetch.mockReset()
vi.stubEnv('EZY_ACTIVITY', 'test-activity')
vi.stubEnv('EZY_PARTNER_KEY', 'test-partner-key')
const mod = await import('@/lib/ezy')
getTariffs = mod.getTariffs
createPayment = mod.createPayment
})
const sampleTariffs = [
{ id: 1, name: 'Adult', price: 250 },
{ id: 2, name: 'Child', price: 150 },
]
describe('getTariffs', () => {
it('fetches and returns tariffs on success', async () => {
mockFetch.mockResolvedValueOnce(okResponse(sampleTariffs))
const result = await getTariffs()
expect(result).toEqual(sampleTariffs)
expect(mockFetch).toHaveBeenCalledOnce()
})
it('returns cached data on second call within 5 minutes', async () => {
mockFetch.mockResolvedValueOnce(okResponse(sampleTariffs))
await getTariffs()
await getTariffs() // should hit cache
expect(mockFetch).toHaveBeenCalledOnce()
})
it('throws when EZY_ACTIVITY env var is missing', async () => {
vi.stubEnv('EZY_ACTIVITY', '')
vi.resetModules()
const mod = await import('@/lib/ezy')
await expect(mod.getTariffs()).rejects.toThrow('EZY_ACTIVITY')
})
it('throws when the API returns a non-ok status', async () => {
mockFetch.mockResolvedValueOnce(errorResponse(503))
await expect(getTariffs()).rejects.toThrow('503')
})
it('throws when the API response fails Zod validation', async () => {
// Missing required `price` field
mockFetch.mockResolvedValueOnce(okResponse([{ id: 1, name: 'Bad' }]))
await expect(getTariffs()).rejects.toThrow()
})
it('retries up to 2 times on network failure, then throws', async () => {
const err = new Error('network error')
mockFetch.mockRejectedValue(err)
await expect(getTariffs()).rejects.toThrow('network error')
// 1 initial + 2 retries = 3 calls
expect(mockFetch).toHaveBeenCalledTimes(3)
})
it('re-fetches from API after the 5-minute cache TTL has expired', async () => {
vi.useFakeTimers()
mockFetch.mockResolvedValue(okResponse(sampleTariffs))
await getTariffs() // populates cache
expect(mockFetch).toHaveBeenCalledTimes(1)
vi.advanceTimersByTime(5 * 60 * 1_000 + 1) // expire cache
await getTariffs()
expect(mockFetch).toHaveBeenCalledTimes(2)
vi.useRealTimers()
})
})
describe('createPayment', () => {
it('returns the payment URL on success', async () => {
mockFetch.mockResolvedValueOnce(okResponse({ url: 'https://pay.example.com/order/123' }))
const result = await createPayment('user@example.com', [{ tariff: 't1', count: '2' }])
expect(result.url).toBe('https://pay.example.com/order/123')
})
it('throws when EZY_PARTNER_KEY env var is missing', async () => {
vi.stubEnv('EZY_PARTNER_KEY', '')
vi.resetModules()
const mod = await import('@/lib/ezy')
await expect(
mod.createPayment('user@example.com', [{ tariff: 't1', count: '1' }])
).rejects.toThrow('EZY_PARTNER_KEY')
})
it('throws when the API returns a non-ok status', async () => {
mockFetch.mockResolvedValueOnce(errorResponse(400))
await expect(createPayment('user@example.com', [{ tariff: 't1', count: '1' }])).rejects.toThrow(
'400'
)
})
it('throws when the API response fails Zod validation (missing url)', async () => {
mockFetch.mockResolvedValueOnce(okResponse({ form: null }))
await expect(
createPayment('user@example.com', [{ tariff: 't1', count: '1' }])
).rejects.toThrow()
})
it('includes email and items in the POST body', async () => {
mockFetch.mockResolvedValueOnce(okResponse({ url: 'https://pay.example.com/1' }))
await createPayment('buyer@example.com', [{ tariff: 'vip', count: '3' }])
const [, init] = mockFetch.mock.calls[0] as [string, RequestInit & { body: FormData }]
const body = init.body as FormData
expect(body.get('email')).toBe('buyer@example.com')
expect(body.get('partner_key')).toBe('test-partner-key')
})
it('retries up to 2 times on network failure, then throws', async () => {
mockFetch.mockRejectedValue(new Error('network error'))
await expect(
createPayment('user@example.com', [{ tariff: 't1', count: '1' }])
).rejects.toThrow('network error')
expect(mockFetch).toHaveBeenCalledTimes(3)
})
it('serializes items as JSON in the order FormData field', async () => {
mockFetch.mockResolvedValueOnce(okResponse({ url: 'https://pay.example.com/2' }))
await createPayment('buyer@example.com', [
{ tariff: 'adult', count: '2' },
{ tariff: 'child', count: '1' },
])
const [, init] = mockFetch.mock.calls[0] as [string, RequestInit & { body: FormData }]
const order = JSON.parse((init.body as FormData).get('order') as string)
expect(order).toEqual([
{ tariff: 'adult', count: '2' },
{ tariff: 'child', count: '1' },
])
})
})