Add comprehensive route-level unit tests
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Has been cancelled
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:
parent
f696cb36db
commit
a20828b09c
4 changed files with 1255 additions and 0 deletions
262
src/services/__tests__/watermark.test.ts
Normal file
262
src/services/__tests__/watermark.test.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue