Add comprehensive route-level unit tests
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Has been cancelled

- Add playground.test.ts with 14 tests for playground endpoint
- Add screenshot.test.ts with 17 tests for screenshot endpoint
- Add health.test.ts with 7 tests for health endpoint
- Add watermark.test.ts with 14 tests for watermark service

Total: 52 new tests covering:
- Input validation and error handling
- Authentication and authorization scenarios
- Caching behavior and cache bypass
- Parameter normalization and limits
- SSRF protection and blocked URLs
- Service error conditions (timeouts, queue full)
- Browser pool integration
- Watermark image processing logic

All tests pass and use proper mocking of dependencies.
This commit is contained in:
SnapAPI Test Agent 2026-02-25 08:05:53 +00:00
parent f696cb36db
commit a20828b09c
4 changed files with 1255 additions and 0 deletions

View file

@ -0,0 +1,262 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { addWatermark } from '../watermark.js'
// Mock browser service
vi.mock('../browser.js', () => ({
acquirePage: vi.fn(),
releasePage: vi.fn()
}))
const { acquirePage, releasePage } = await import('../browser.js')
const mockAcquirePage = vi.mocked(acquirePage)
const mockReleasePage = vi.mocked(releasePage)
function createMockPage() {
return {
setViewport: vi.fn(),
setContent: vi.fn(),
screenshot: vi.fn()
}
}
function createMockInstance() {
return { id: 'test-instance' }
}
describe('Watermark Service', () => {
beforeEach(() => {
vi.clearAllMocks()
})
afterEach(() => {
vi.restoreAllMocks()
})
describe('addWatermark', () => {
it('should add watermark to image buffer', async () => {
const mockPage = createMockPage()
const mockInstance = createMockInstance()
const inputBuffer = Buffer.from('input-image-data')
const outputBuffer = Buffer.from('watermarked-image-data')
mockAcquirePage.mockResolvedValueOnce({ page: mockPage, instance: mockInstance })
mockPage.screenshot.mockResolvedValueOnce(outputBuffer as any)
const result = await addWatermark(inputBuffer, 1280, 800)
expect(mockAcquirePage).toHaveBeenCalledOnce()
expect(mockPage.setViewport).toHaveBeenCalledWith({ width: 1280, height: 800 })
expect(mockPage.setContent).toHaveBeenCalledWith(
expect.stringContaining('data:image/png;base64,'),
{ waitUntil: "load" }
)
expect(mockPage.screenshot).toHaveBeenCalledWith({
type: "png",
encoding: "binary"
})
expect(mockReleasePage).toHaveBeenCalledWith(mockPage, mockInstance)
expect(result).toBeInstanceOf(Buffer)
expect(result).toEqual(outputBuffer)
})
it('should set viewport to specified dimensions', async () => {
const mockPage = createMockPage()
const mockInstance = createMockInstance()
const inputBuffer = Buffer.from('test-image')
mockAcquirePage.mockResolvedValueOnce({ page: mockPage, instance: mockInstance })
mockPage.screenshot.mockResolvedValueOnce(Buffer.from('result') as any)
await addWatermark(inputBuffer, 1920, 1080)
expect(mockPage.setViewport).toHaveBeenCalledWith({ width: 1920, height: 1080 })
})
it('should include base64 encoded image in HTML content', async () => {
const mockPage = createMockPage()
const mockInstance = createMockInstance()
const inputBuffer = Buffer.from('test-image-data')
mockAcquirePage.mockResolvedValueOnce({ page: mockPage, instance: mockInstance })
mockPage.screenshot.mockResolvedValueOnce(Buffer.from('result') as any)
await addWatermark(inputBuffer, 800, 600)
const expectedBase64 = inputBuffer.toString('base64')
const setContentCall = mockPage.setContent.mock.calls[0][0]
expect(setContentCall).toContain(`data:image/png;base64,${expectedBase64}`)
})
it('should include watermark text in HTML content', async () => {
const mockPage = createMockPage()
const mockInstance = createMockInstance()
const inputBuffer = Buffer.from('test')
mockAcquirePage.mockResolvedValueOnce({ page: mockPage, instance: mockInstance })
mockPage.screenshot.mockResolvedValueOnce(Buffer.from('result') as any)
await addWatermark(inputBuffer, 1000, 700)
const setContentCall = mockPage.setContent.mock.calls[0][0]
expect(setContentCall).toContain('snapapi.eu — upgrade for clean screenshots')
})
it('should scale font size based on image width', async () => {
const mockPage = createMockPage()
const mockInstance = createMockInstance()
const inputBuffer = Buffer.from('test')
mockAcquirePage.mockResolvedValueOnce({ page: mockPage, instance: mockInstance })
mockPage.screenshot.mockResolvedValueOnce(Buffer.from('result') as any)
await addWatermark(inputBuffer, 2000, 1000)
const setContentCall = mockPage.setContent.mock.calls[0][0]
const expectedFontSize = Math.max(2000 / 20, 24) // 100px for 2000px width
expect(setContentCall).toContain(`font-size: ${expectedFontSize}px`)
})
it('should enforce minimum font size', async () => {
const mockPage = createMockPage()
const mockInstance = createMockInstance()
const inputBuffer = Buffer.from('test')
mockAcquirePage.mockResolvedValueOnce({ page: mockPage, instance: mockInstance })
mockPage.screenshot.mockResolvedValueOnce(Buffer.from('result') as any)
// Small width that would result in font size < 24px
await addWatermark(inputBuffer, 400, 300)
const setContentCall = mockPage.setContent.mock.calls[0][0]
// Should use minimum font size of 24px
expect(setContentCall).toContain('font-size: 24px')
})
it('should include CSS styling for watermark', async () => {
const mockPage = createMockPage()
const mockInstance = createMockInstance()
const inputBuffer = Buffer.from('test')
mockAcquirePage.mockResolvedValueOnce({ page: mockPage, instance: mockInstance })
mockPage.screenshot.mockResolvedValueOnce(Buffer.from('result') as any)
await addWatermark(inputBuffer, 1200, 800)
const setContentCall = mockPage.setContent.mock.calls[0][0]
// Check for key CSS properties
expect(setContentCall).toContain('transform: rotate(-30deg)')
expect(setContentCall).toContain('color: rgba(255, 255, 255, 0.35)')
expect(setContentCall).toContain('text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.5)')
expect(setContentCall).toContain('font-weight: 900')
expect(setContentCall).toContain('pointer-events: none')
})
it('should wait for page load before taking screenshot', async () => {
const mockPage = createMockPage()
const mockInstance = createMockInstance()
const inputBuffer = Buffer.from('test')
mockAcquirePage.mockResolvedValueOnce({ page: mockPage, instance: mockInstance })
mockPage.screenshot.mockResolvedValueOnce(Buffer.from('result') as any)
await addWatermark(inputBuffer, 1000, 600)
expect(mockPage.setContent).toHaveBeenCalledWith(
expect.any(String),
{ waitUntil: "load" }
)
})
it('should release page even if screenshot fails', async () => {
const mockPage = createMockPage()
const mockInstance = createMockInstance()
const inputBuffer = Buffer.from('test')
mockAcquirePage.mockResolvedValueOnce({ page: mockPage, instance: mockInstance })
mockPage.screenshot.mockRejectedValueOnce(new Error('Screenshot failed'))
await expect(addWatermark(inputBuffer, 800, 600)).rejects.toThrow('Screenshot failed')
expect(mockReleasePage).toHaveBeenCalledWith(mockPage, mockInstance)
})
it('should release page even if setContent fails', async () => {
const mockPage = createMockPage()
const mockInstance = createMockInstance()
const inputBuffer = Buffer.from('test')
mockAcquirePage.mockResolvedValueOnce({ page: mockPage, instance: mockInstance })
mockPage.setContent.mockRejectedValueOnce(new Error('SetContent failed'))
await expect(addWatermark(inputBuffer, 800, 600)).rejects.toThrow('SetContent failed')
expect(mockReleasePage).toHaveBeenCalledWith(mockPage, mockInstance)
})
it('should handle page acquisition failure', async () => {
mockAcquirePage.mockRejectedValueOnce(new Error('No pages available'))
await expect(addWatermark(Buffer.from('test'), 800, 600))
.rejects.toThrow('No pages available')
expect(mockReleasePage).not.toHaveBeenCalled()
})
it('should generate valid HTML structure', async () => {
const mockPage = createMockPage()
const mockInstance = createMockInstance()
const inputBuffer = Buffer.from('test')
mockAcquirePage.mockResolvedValueOnce({ page: mockPage, instance: mockInstance })
mockPage.screenshot.mockResolvedValueOnce(Buffer.from('result') as any)
await addWatermark(inputBuffer, 1000, 700)
const html = mockPage.setContent.mock.calls[0][0]
// Check HTML structure
expect(html).toContain('<!DOCTYPE html>')
expect(html).toContain('<html>')
expect(html).toContain('<head>')
expect(html).toContain('<style>')
expect(html).toContain('<body>')
expect(html).toContain('<img')
expect(html).toContain('<div class="watermark">')
expect(html).toContain('</html>')
})
it('should set correct body dimensions', async () => {
const mockPage = createMockPage()
const mockInstance = createMockInstance()
const inputBuffer = Buffer.from('test')
mockAcquirePage.mockResolvedValueOnce({ page: mockPage, instance: mockInstance })
mockPage.screenshot.mockResolvedValueOnce(Buffer.from('result') as any)
await addWatermark(inputBuffer, 1500, 900)
const html = mockPage.setContent.mock.calls[0][0]
expect(html).toContain('width: 1500px; height: 900px')
})
it('should handle various image buffer sizes', async () => {
const mockPage = createMockPage()
const mockInstance = createMockInstance()
const largeBuffer = Buffer.alloc(1024 * 1024, 'test') // 1MB buffer
mockAcquirePage.mockResolvedValueOnce({ page: mockPage, instance: mockInstance })
mockPage.screenshot.mockResolvedValueOnce(Buffer.from('result') as any)
const result = await addWatermark(largeBuffer, 2000, 1200)
expect(mockPage.setContent).toHaveBeenCalled()
expect(result).toBeInstanceOf(Buffer)
})
})
})