Skip to content

Commit 1020831

Browse files
authored
tests(api) add script to test the claim token api (#320)
1 parent f092eaa commit 1020831

File tree

7 files changed

+1119
-12
lines changed

7 files changed

+1119
-12
lines changed

.github/workflows/e2e.yml

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: E2E Test
1+
name: e2e
22

33
concurrency:
44
group: "${{ github.workflow }}-${{ github.ref }}"
@@ -20,9 +20,32 @@ env:
2020
RUSTC_WRAPPER: sccache
2121
CC: sccache clang
2222
CXX: sccache clang++
23+
2324
jobs:
2425
e2e:
2526
runs-on: ubuntu-latest
27+
# Each 'include' entry will run as a separate job with its own parameters.
28+
strategy:
29+
fail-fast: false
30+
matrix:
31+
include:
32+
- name: "Browser Tests"
33+
script: "e2e/script.js"
34+
port: 8787
35+
k6_browser_enabled: true
36+
state_dir: "browser"
37+
- name: "API Tests"
38+
script: "e2e/test_claim_token_api.js"
39+
port: 8787
40+
k6_browser_enabled: false
41+
state_dir: "api"
42+
- name: "Claim Token API CORS Tests"
43+
script: "e2e/test_cors.js"
44+
port: 8787
45+
k6_browser_enabled: false
46+
state_dir: "cors"
47+
48+
name: "E2E ${{ matrix.name }}"
2649
steps:
2750
- name: Setup sccache
2851
uses: mozilla-actions/sccache-action@v0.0.9
@@ -32,22 +55,37 @@ jobs:
3255
- name: Checkout code
3356
uses: actions/checkout@v5
3457

58+
- name: Cache dependencies
59+
uses: actions/cache@v4
60+
with:
61+
path: |
62+
~/.cargo/bin/
63+
~/.cargo/registry/index/
64+
~/.cargo/registry/cache/
65+
~/.cargo/git/db/
66+
target/
67+
~/.npm
68+
key: ${{ runner.os }}-deps-${{ hashFiles('**/yarn.lock', '**/Cargo.lock') }}
69+
restore-keys: |
70+
${{ runner.os }}-deps-
71+
3572
- name: Install wasm-pack
3673
run:
3774
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
3875

39-
- uses: sigoden/install-binary@v1
76+
- name: Install wasm-opt
77+
uses: sigoden/install-binary@v1
4078
with:
4179
repo: WebAssembly/binaryen
4280
name: wasm-opt
4381

4482
- name: Install worker-build
45-
run: cargo install --locked worker-build
83+
run: cargo install --locked --force worker-build
4684

47-
- name: Set up k6 (with browser)
85+
- name: Set up k6
4886
uses: grafana/setup-k6-action@v1
4987
with:
50-
browser: true
88+
browser: ${{ matrix.k6_browser_enabled }}
5189

5290
- name: Set up secrets
5391
shell: bash
@@ -56,7 +94,7 @@ jobs:
5694
echo "SECRET_MAINNET_WALLET=${{ secrets.TEST_MAINNET_PRIVATE_KEY_HEX }}" >> .dev.vars
5795
echo "SECRET_CALIBNET_USDFC_WALLET=${{ secrets.TEST_CALIBNET_USDFC_PRIVATE_KEY_HEX }}" >> .dev.vars
5896
59-
- name: Run website
97+
- name: Build and run website
6098
run: |
6199
# These might or might not be the same as used for deployment. They are used strictly for testing purposes.
62100
# Note: those can't be put directly as environment variables in GH Actions (without a default value) due to
@@ -69,11 +107,17 @@ jobs:
69107
corepack enable
70108
yarn --immutable
71109
yarn build
72-
yarn start &
73-
echo "waiting"
74-
timeout 120 sh -c 'until nc -z $0 $1; do sleep 1; done' 127.0.0.1 8787
110+
# Use separate state directories to isolate Durable Object storage between parallel runs
111+
yarn wrangler dev --port ${{ matrix.port }} --persist-to .wrangler-state-${{ matrix.state_dir }}-${{ github.run_id }} &
112+
echo "waiting for server on port ${{ matrix.port }}"
113+
timeout 120 sh -c 'until nc -z $0 $1; do sleep 1; done' 127.0.0.1 ${{ matrix.port }}
114+
echo "TCP port check passed, now waiting for HTTP server to be ready..."
115+
timeout 120 sh -c 'until curl -s --max-time 30 http://127.0.0.1:${{ matrix.port }} > /dev/null; do echo "Waiting for HTTP server (first request may take 60-90s)..."; sleep 5; done'
116+
echo "Server is ready!"
75117
76-
- name: Run k6 E2E script
118+
- name: Run k6 E2E Test
77119
uses: grafana/run-k6-action@v1
78120
with:
79-
path: 'e2e/script.js'
121+
path: ${{ matrix.script }}
122+
env:
123+
API_URL: "http://127.0.0.1:${{ matrix.port }}"

docs/cors_testing_guide.md

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
# CORS Testing Guide
2+
3+
## Key Terms
4+
5+
**CORS (Cross-Origin Resource Sharing)**: Browser security mechanism that
6+
controls whether web pages from one domain can access resources from another
7+
domain.
8+
9+
**Preflight Request**: An OPTIONS request browsers send before the actual
10+
request to check if the cross-origin request is permitted.
11+
12+
**Origin Header**: Identifies the domain making the request (e.g.,
13+
`https://app.example.com`). Automatically set by browsers, cannot be overridden
14+
in JavaScript.
15+
16+
**Access-Control-Allow-\* Headers**: Server response headers that tell browsers
17+
which cross-origin requests are permitted.
18+
19+
---
20+
21+
## What We're Testing
22+
23+
### CORS Configuration for Public API Access
24+
25+
Testing that the `/api/claim_token` endpoint properly supports cross-origin
26+
requests from any domain:
27+
28+
- **CalibnetFIL, CalibnetUSDFC, MainnetFIL** faucets accessible from external
29+
websites
30+
- **Browser-based DApps** can integrate with the faucet API
31+
- **Third-party integrations** work without CORS errors
32+
33+
### Current Configuration
34+
35+
```rust
36+
// src/lib.rs
37+
let cors = CorsLayer::new()
38+
.allow_origin(Any) // Accept requests from ANY origin
39+
.allow_methods(Method::GET); // Only GET requests allowed
40+
```
41+
42+
**Result**: `Access-Control-Allow-Origin: *` header on all responses
43+
44+
**Note**: We don't set `allow_headers()` because GET requests only use "simple
45+
headers" (like `Accept`, `User-Agent`) which don't require CORS preflight
46+
permission.
47+
48+
---
49+
50+
## How We Test in CI
51+
52+
### Matrix Strategy with Isolated State
53+
54+
```yaml
55+
- name: "CORS Tests"
56+
script: "e2e/test_cors.js"
57+
port: 8788 # Separate port from Browser/API tests
58+
k6_browser_enabled: false # No browser support needed
59+
state_dir: "cors" # Isolated Durable Object state
60+
```
61+
62+
Each test suite runs independently:
63+
64+
```bash
65+
yarn wrangler dev --port 8788 --persist-to .wrangler-state-cors-${github.run_id}
66+
```
67+
68+
This prevents CORS test state from interfering with Browser or API tests.
69+
70+
---
71+
72+
## Test Flow
73+
74+
### 1. Preflight OPTIONS Request (5 checks)
75+
76+
Validates that browsers can check permissions before sending actual requests:
77+
78+
```javascript
79+
// Request
80+
OPTIONS /api/claim_token
81+
Origin: https://external-example.com
82+
Access-Control-Request-Method: GET
83+
84+
// Validates
85+
✓ Status is 200 or 204
86+
✓ Has Access-Control-Allow-Origin header
87+
✓ Allows all origins (*)
88+
✓ Has Access-Control-Allow-Methods header
89+
✓ Allows GET method
90+
```
91+
92+
### 2. Actual Cross-Origin GET Request (3 checks)
93+
94+
Confirms the API properly responds to cross-origin requests:
95+
96+
```javascript
97+
// Request
98+
GET /api/claim_token?faucet_info=CalibnetFIL&address=0x...
99+
Origin: https://external-example.com
100+
101+
// Validates
102+
✓ Has Access-Control-Allow-Origin in response
103+
CORS header allows all origins (*)
104+
✓ Response received (not blocked by CORS)
105+
```
106+
107+
### 3. Same-Origin Request (2 checks)
108+
109+
Verifies CORS headers are present even without explicit origin:
110+
111+
```javascript
112+
// Request (no Origin header)
113+
GET /api/claim_token?faucet_info=CalibnetFIL&address=0x...
114+
115+
// Validates
116+
✓ Request succeeds (200 or 429)
117+
✓ Has Access-Control-Allow-Origin header
118+
```
119+
120+
### 4. Multiple Origins (9 checks)
121+
122+
Tests that different domains can all access the API:
123+
124+
```javascript
125+
// Tests three different origins:
126+
- https://app.example.com
127+
- http://localhost:3000
128+
- https://wallet.filecoin.io
129+
130+
// For each origin:
131+
✓ Request succeeds
132+
CORS allows origin
133+
✓ Response received
134+
```
135+
136+
### 5. Security & Edge Cases (3 checks)
137+
138+
Validates security headers and error handling:
139+
140+
```javascript
141+
// Security
142+
✓ Access-Control-Allow-Credentials is NOT set (safer for public APIs)
143+
144+
// Error responses
145+
CORS headers present even on 500/400 errors (critical for browser error handling)
146+
```
147+
148+
**Total: 22 checks covering full CORS compliance**
149+
150+
---
151+
152+
## Running Tests Locally
153+
154+
```bash
155+
# Start server
156+
yarn wrangler dev --port 8787
157+
158+
# Run CORS tests
159+
API_URL="http://127.0.0.1:8787" k6 run e2e/test_cors.js
160+
```
161+
162+
### Expected Output (All Passing)
163+
164+
```
165+
checks_succeeded...: 100.00% 22 out of 22
166+
167+
✓ Preflight: Status is 200 or 204
168+
✓ Preflight: Has Access-Control-Allow-Origin header
169+
✓ Preflight: Allows all origins (*)
170+
✓ Preflight: Has Access-Control-Allow-Methods
171+
✓ Preflight: Allows GET method
172+
✓ Actual Request: Has Access-Control-Allow-Origin in response
173+
✓ Actual Request: CORS header allows all origins
174+
✓ Actual Request: Response received (not blocked by CORS)
175+
✓ Same-Origin: Request succeeds
176+
✓ Same-Origin: Has Access-Control-Allow-Origin (even for same-origin)
177+
✓ Multiple Origins: https://app.example.com - Request succeeds
178+
✓ Multiple Origins: https://app.example.com - CORS allows origin
179+
✓ Multiple Origins: https://app.example.com - Response received
180+
✓ Multiple Origins: http://localhost:3000 - Request succeeds
181+
✓ Multiple Origins: http://localhost:3000 - CORS allows origin
182+
✓ Multiple Origins: http://localhost:3000 - Response received
183+
✓ Multiple Origins: https://wallet.filecoin.io - Request succeeds
184+
✓ Multiple Origins: https://wallet.filecoin.io - CORS allows origin
185+
✓ Multiple Origins: https://wallet.filecoin.io - Response received
186+
✓ Security: Has Access-Control-Allow-Origin
187+
✓ Security: Access-Control-Allow-Credentials is not set
188+
✓ Error Response: CORS headers present even on errors
189+
```
190+
191+
**Note**: Total of 22 checks (5 preflight + 3 actual + 2 same-origin + 9
192+
multiple-origins + 3 security/edge cases)
193+
194+
### Common Test Results
195+
196+
**All CORS checks pass, but some "Request succeeds" fail:**
197+
198+
- ✅ CORS is working correctly
199+
- ⚠️ Server returning 500 errors (unrelated to CORS)
200+
- Check server logs for the actual API error
201+
202+
**CORS header checks failing:**
203+
204+
- ❌ Configuration issue in `src/lib.rs`
205+
- Verify `allow_origin(Any)` and `allow_headers(Any)` are set
206+
- Confirm `CorsLayer` is applied to the router
207+
208+
---
209+
210+
## Quick Manual Test
211+
212+
### Browser DevTools (Simplest)
213+
214+
1. Open browser console (F12) on any page
215+
2. Run:
216+
217+
```javascript
218+
fetch(
219+
"http://127.0.0.1:8787/api/claim_token?faucet_info=CalibnetFIL&address=f1pxxbe7he3c6vcw5as3gfvq33kprpmlufgtjgfdq",
220+
)
221+
.then((r) => console.log("✅ CORS works! Status:", r.status))
222+
.catch((err) => console.error("❌ CORS blocked:", err));
223+
```
224+
225+
### curl (For CI/Scripts)
226+
227+
```bash
228+
# Test preflight
229+
curl -i -X OPTIONS \
230+
-H "Origin: https://example.com" \
231+
-H "Access-Control-Request-Method: GET" \
232+
http://127.0.0.1:8787/api/claim_token
233+
234+
# Look for: Access-Control-Allow-Origin: *
235+
```
236+
237+
---
238+
239+
## Why CORS Matters
240+
241+
### Before CORS (Blocked)
242+
243+
```javascript
244+
// From https://my-dapp.com
245+
fetch("https://faucet.chainsafe.io/api/claim_token?...");
246+
// ❌ Error: CORS policy: No 'Access-Control-Allow-Origin' header
247+
```
248+
249+
### After CORS (Allowed)
250+
251+
```javascript
252+
// From https://my-dapp.com
253+
fetch("https://faucet.chainsafe.io/api/claim_token?...");
254+
// ✅ Success: Response includes "Access-Control-Allow-Origin: *"
255+
```
256+
257+
**Security**: Protected by rate limiting (60s cooldown, 2-drip wallet cap), not
258+
CORS restrictions.

0 commit comments

Comments
 (0)