Initial commit
This commit is contained in:
311
templates/turnstile-test-config.ts
Normal file
311
templates/turnstile-test-config.ts
Normal file
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* Turnstile Testing Configuration
|
||||
*
|
||||
* Use dummy sitekeys and secret keys for automated testing
|
||||
* to avoid hitting rate limits and ensure predictable behavior.
|
||||
*
|
||||
* Official Docs: https://developers.cloudflare.com/turnstile/troubleshooting/testing/
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dummy Sitekeys (Client-Side)
|
||||
*
|
||||
* These can be used from any domain, including localhost.
|
||||
* Production secret keys will reject dummy tokens.
|
||||
*/
|
||||
export const TEST_SITEKEYS = {
|
||||
/** Always passes - visible widget */
|
||||
ALWAYS_PASS: '1x00000000000000000000AA',
|
||||
|
||||
/** Always blocks - visible widget */
|
||||
ALWAYS_BLOCK: '2x00000000000000000000AB',
|
||||
|
||||
/** Always passes - invisible widget */
|
||||
ALWAYS_PASS_INVISIBLE: '1x00000000000000000000BB',
|
||||
|
||||
/** Always blocks - invisible widget */
|
||||
ALWAYS_BLOCK_INVISIBLE: '2x00000000000000000000BB',
|
||||
|
||||
/** Forces an interactive challenge - visible widget */
|
||||
FORCE_INTERACTIVE: '3x00000000000000000000FF',
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Dummy Secret Keys (Server-Side)
|
||||
*
|
||||
* These only accept the dummy token XXXX.DUMMY.TOKEN.XXXX
|
||||
* Real tokens will fail validation with these keys.
|
||||
*/
|
||||
export const TEST_SECRET_KEYS = {
|
||||
/** Always returns success: true */
|
||||
ALWAYS_PASS: '1x0000000000000000000000000000000AA',
|
||||
|
||||
/** Always returns success: false */
|
||||
ALWAYS_FAIL: '2x0000000000000000000000000000000AA',
|
||||
|
||||
/** Returns "token already spent" error */
|
||||
TOKEN_SPENT: '3x0000000000000000000000000000000AA',
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Dummy Token
|
||||
*
|
||||
* Generated by test sitekeys.
|
||||
* Only valid with test secret keys.
|
||||
*/
|
||||
export const DUMMY_TOKEN = 'XXXX.DUMMY.TOKEN.XXXX'
|
||||
|
||||
/**
|
||||
* Environment Detection
|
||||
*
|
||||
* Helper to determine if we're in a test environment
|
||||
*/
|
||||
export function isTestEnvironment(request: Request): boolean {
|
||||
// Check for test headers
|
||||
if (request.headers.get('x-test-environment') === 'true') {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check for localhost/test IPs
|
||||
const ip = request.headers.get('CF-Connecting-IP') || ''
|
||||
const testIPs = ['127.0.0.1', '::1', 'localhost']
|
||||
if (testIPs.includes(ip)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check for test query parameter
|
||||
const url = new URL(request.url)
|
||||
if (url.searchParams.get('test') === 'true') {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Test Credentials
|
||||
*
|
||||
* Returns test or production credentials based on environment
|
||||
*/
|
||||
export function getTurnstileCredentials(
|
||||
request: Request,
|
||||
env: {
|
||||
TURNSTILE_SITE_KEY?: string
|
||||
TURNSTILE_SECRET_KEY?: string
|
||||
}
|
||||
) {
|
||||
if (isTestEnvironment(request)) {
|
||||
return {
|
||||
sitekey: TEST_SITEKEYS.ALWAYS_PASS,
|
||||
secretKey: TEST_SECRET_KEYS.ALWAYS_PASS,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sitekey: env.TURNSTILE_SITE_KEY || '',
|
||||
secretKey: env.TURNSTILE_SECRET_KEY || '',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Playwright Test Example
|
||||
*/
|
||||
/*
|
||||
// playwright.config.ts
|
||||
export default {
|
||||
use: {
|
||||
baseURL: 'http://localhost:5173',
|
||||
extraHTTPHeaders: {
|
||||
'x-test-environment': 'true',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// test/turnstile.spec.ts
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test('form submission with Turnstile', async ({ page }) => {
|
||||
await page.goto('/contact')
|
||||
|
||||
// Fill form
|
||||
await page.fill('input[name="email"]', 'test@example.com')
|
||||
await page.fill('textarea[name="message"]', 'Test message')
|
||||
|
||||
// Turnstile auto-solves with dummy token in test mode
|
||||
await page.click('button[type="submit"]')
|
||||
|
||||
// Verify success
|
||||
await expect(page.locator('.success-message')).toBeVisible()
|
||||
})
|
||||
*/
|
||||
|
||||
/**
|
||||
* Cypress Test Example
|
||||
*/
|
||||
/*
|
||||
// cypress/e2e/turnstile.cy.ts
|
||||
describe('Turnstile Form', () => {
|
||||
beforeEach(() => {
|
||||
// Set test header
|
||||
cy.intercept('**', (req) => {
|
||||
req.headers['x-test-environment'] = 'true'
|
||||
})
|
||||
})
|
||||
|
||||
it('submits form successfully', () => {
|
||||
cy.visit('/contact')
|
||||
|
||||
cy.get('input[name="email"]').type('test@example.com')
|
||||
cy.get('textarea[name="message"]').type('Test message')
|
||||
|
||||
// Turnstile auto-solves in test mode
|
||||
cy.get('button[type="submit"]').click()
|
||||
|
||||
cy.contains('Success').should('be.visible')
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
/**
|
||||
* Jest Mock Example
|
||||
*/
|
||||
/*
|
||||
// jest.setup.ts
|
||||
jest.mock('@marsidev/react-turnstile', () => ({
|
||||
Turnstile: ({ onSuccess }: { onSuccess: (token: string) => void }) => {
|
||||
// Auto-solve with dummy token
|
||||
React.useEffect(() => {
|
||||
onSuccess('XXXX.DUMMY.TOKEN.XXXX')
|
||||
}, [onSuccess])
|
||||
return <div data-testid="turnstile-mock" />
|
||||
},
|
||||
}))
|
||||
|
||||
// component.test.tsx
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import { ContactForm } from './ContactForm'
|
||||
|
||||
test('submits form with Turnstile', async () => {
|
||||
render(<ContactForm />)
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Email'), {
|
||||
target: { value: 'test@example.com' },
|
||||
})
|
||||
|
||||
// Turnstile auto-solves
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Submit' }))
|
||||
|
||||
expect(await screen.findByText('Success')).toBeInTheDocument()
|
||||
})
|
||||
*/
|
||||
|
||||
/**
|
||||
* Server-Side Test Example (Vitest)
|
||||
*/
|
||||
/*
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { validateTurnstile } from './turnstile-server-validation'
|
||||
import { TEST_SECRET_KEYS, DUMMY_TOKEN } from './turnstile-test-config'
|
||||
|
||||
describe('Turnstile Validation', () => {
|
||||
it('validates dummy token with test secret', async () => {
|
||||
const result = await validateTurnstile(
|
||||
DUMMY_TOKEN,
|
||||
TEST_SECRET_KEYS.ALWAYS_PASS
|
||||
)
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects real token with test secret', async () => {
|
||||
const realToken = 'some-real-token-from-production'
|
||||
|
||||
const result = await validateTurnstile(
|
||||
realToken,
|
||||
TEST_SECRET_KEYS.ALWAYS_PASS
|
||||
)
|
||||
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
/**
|
||||
* Environment-Aware Component (React)
|
||||
*/
|
||||
/*
|
||||
import { Turnstile } from '@marsidev/react-turnstile'
|
||||
import { TEST_SITEKEYS } from './turnstile-test-config'
|
||||
|
||||
function MyForm() {
|
||||
const sitekey = process.env.NODE_ENV === 'test'
|
||||
? TEST_SITEKEYS.ALWAYS_PASS
|
||||
: process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY!
|
||||
|
||||
return (
|
||||
<form>
|
||||
<Turnstile
|
||||
siteKey={sitekey}
|
||||
onSuccess={(token) => console.log('Token:', token)}
|
||||
/>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Cloudflare Workers Test Example
|
||||
*/
|
||||
/*
|
||||
import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test'
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import worker from '../src/index'
|
||||
import { DUMMY_TOKEN } from './turnstile-test-config'
|
||||
|
||||
describe('Worker with Turnstile', () => {
|
||||
it('validates test token', async () => {
|
||||
const formData = new FormData()
|
||||
formData.append('email', 'test@example.com')
|
||||
formData.append('cf-turnstile-response', DUMMY_TOKEN)
|
||||
|
||||
const request = new Request('http://localhost/api/contact', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'x-test-environment': 'true',
|
||||
},
|
||||
})
|
||||
|
||||
const ctx = createExecutionContext()
|
||||
const response = await worker.fetch(request, env, ctx)
|
||||
await waitOnExecutionContext(ctx)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
/**
|
||||
* CI/CD Environment Variables
|
||||
*
|
||||
* Set these in your CI/CD pipeline:
|
||||
*/
|
||||
/*
|
||||
# GitHub Actions
|
||||
env:
|
||||
TURNSTILE_SITE_KEY: 1x00000000000000000000AA
|
||||
TURNSTILE_SECRET_KEY: 1x0000000000000000000000000000000AA
|
||||
|
||||
# GitLab CI
|
||||
variables:
|
||||
TURNSTILE_SITE_KEY: "1x00000000000000000000AA"
|
||||
TURNSTILE_SECRET_KEY: "1x0000000000000000000000000000000AA"
|
||||
*/
|
||||
|
||||
export default {
|
||||
TEST_SITEKEYS,
|
||||
TEST_SECRET_KEYS,
|
||||
DUMMY_TOKEN,
|
||||
isTestEnvironment,
|
||||
getTurnstileCredentials,
|
||||
}
|
||||
Reference in New Issue
Block a user