Skip to content

Commit c61b716

Browse files
committed
split integration test for file/single and redis/shared
1 parent dca71e7 commit c61b716

8 files changed

Lines changed: 684 additions & 324 deletions

File tree

.github/workflows/lint-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
6666

6767
- name: run
68-
run: go run test.go
68+
run: go run .
6969
working-directory: ./ci
7070
env:
7171
TRAEFIK_TAG: ${{ matrix.traefik }}
@@ -87,7 +87,7 @@ jobs:
8787
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
8888

8989
- name: run
90-
run: go run test.go
90+
run: go run .
9191
working-directory: ./ci
9292
env:
9393
TRAEFIK_TAG: ${{ matrix.traefik }}

ci/README.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Captcha Protect CI Integration Tests
2+
3+
This directory contains integration tests for the captcha-protect plugin that verify state persistence and sharing functionality.
4+
5+
## Test Modes
6+
7+
There are two separate test modes:
8+
9+
### 1. File-Based State Persistence Test (`test-file.go`)
10+
11+
Tests that state is correctly persisted to a file and reloaded after container restarts.
12+
13+
- **Docker Compose:** `docker-compose-file.yml`
14+
- **Single nginx instance**
15+
- **Tests:**
16+
- Rate limiting works correctly
17+
- State saves to `/tmp/state.json`
18+
- State is reloaded after container restart
19+
- All bot entries are preserved
20+
21+
**Run with:**
22+
```bash
23+
TEST_MODE=file go run .
24+
```
25+
26+
### 2. Redis-Based State Sharing Test (`test-redis.go`)
27+
28+
Tests that state is shared between multiple plugin instances via Redis pub/sub.
29+
30+
- **Docker Compose:** `docker-compose-redis.yml`
31+
- **Two nginx instances** (`nginx` and `nginx2`)
32+
- **Redis instance** for state sharing
33+
- **Tests:**
34+
- Rate limiting works on first instance
35+
- State is shared to second instance via Redis
36+
- Both instances see the same rate limit state in near real-time
37+
- State persists across container restarts via Redis
38+
39+
**Run with:**
40+
```bash
41+
TEST_MODE=redis go run .
42+
```
43+
44+
### 3. Run Both Tests (Default)
45+
46+
If no `TEST_MODE` is specified, both tests run sequentially:
47+
48+
```bash
49+
go run .
50+
```
51+
52+
## Configuration
53+
54+
Environment variables:
55+
56+
- `TEST_MODE` - Test mode: `file`, `redis`, or empty for both
57+
- `RATE_LIMIT` - Rate limit threshold (default: 5)
58+
- `TRAEFIK_TAG` - Traefik Docker image tag (e.g., `v3.0`)
59+
- `NGINX_TAG` - Nginx Docker image tag
60+
- `TURNSTILE_SITE_KEY` - Cloudflare Turnstile site key
61+
- `TURNSTILE_SECRET_KEY` - Cloudflare Turnstile secret key
62+
63+
## How It Works
64+
65+
### File-Based Test
66+
67+
1. Starts Traefik + 1 nginx instance with `persistentStateFile: "/tmp/state.json"`
68+
2. Generates 100 unique public IPs (different /16 subnets)
69+
3. Makes parallel requests from each IP until rate limit is hit
70+
4. Verifies challenge redirect occurs on next request
71+
5. Waits for state to save to disk
72+
6. Restarts containers
73+
7. Verifies all 100 IPs are still in the bot cache (state was reloaded)
74+
75+
### Redis-Based Test
76+
77+
1. Starts Traefik + 2 nginx instances + Redis with `redisAddr: "redis:6379"`
78+
2. Generates 100 unique public IPs
79+
3. Makes parallel requests to first nginx instance until rate limit is hit
80+
4. Verifies challenge redirect on first instance
81+
5. Waits for Redis sync (5 second interval + buffer)
82+
6. Tests same IP on second nginx instance (`/app2`)
83+
7. Verifies second instance also shows rate limit (state was shared via Redis)
84+
8. Restarts both nginx instances
85+
9. Verifies state was reloaded from Redis
86+
87+
## File Structure
88+
89+
```
90+
ci/
91+
├── test.go # Main dispatcher
92+
├── test-file.go # File-based persistence test
93+
├── test-redis.go # Redis-based sharing test
94+
├── docker-compose-file.yml # Single nginx + file persistence
95+
├── docker-compose-redis.yml # Two nginx + Redis
96+
├── conf/nginx/default.conf # Nginx configuration
97+
└── tmp/ # State files (gitignored)
98+
```
99+
100+
## Example Usage
101+
102+
**Important:** Always use `go run .` (not `go run test.go`) to compile all source files in the package.
103+
104+
```bash
105+
# Run only file-based test
106+
TEST_MODE=file go run .
107+
108+
# Run only Redis test
109+
TEST_MODE=redis go run .
110+
111+
# Run both tests
112+
go run .
113+
114+
# Use specific Traefik version
115+
TRAEFIK_TAG=v3.0 TEST_MODE=redis go run .
116+
```
117+
118+
### Common Errors
119+
120+
**Error:** `undefined: runFileBasedTest`
121+
**Solution:** Use `go run .` instead of `go run test.go` to compile all `.go` files.
122+
123+
## CI Integration
124+
125+
The GitHub Actions workflow runs these tests against multiple Traefik versions:
126+
127+
```yaml
128+
- name: Run integration tests
129+
run: |
130+
cd ci
131+
go run .
132+
env:
133+
TRAEFIK_TAG: v3.0
134+
```
135+
136+
Both file and Redis tests run by default in CI to ensure both state persistence mechanisms work correctly.

ci/docker-compose-file.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
networks:
2+
default:
3+
services:
4+
nginx:
5+
image: nginx:${NGINX_TAG}
6+
labels:
7+
traefik.enable: true
8+
traefik.http.routers.nginx.entrypoints: http
9+
traefik.http.routers.nginx.service: nginx
10+
traefik.http.routers.nginx.rule: Host(`localhost`)
11+
traefik.http.services.nginx.loadbalancer.server.port: 80
12+
traefik.http.routers.nginx.middlewares: captcha-protect@docker
13+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.captchaProvider: turnstile
14+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.window: 120
15+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.rateLimit: ${RATE_LIMIT}
16+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.siteKey: ${TURNSTILE_SITE_KEY}
17+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.secretKey: ${TURNSTILE_SECRET_KEY}
18+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.enableStatsPage: "true"
19+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.ipForwardedHeader: "X-Forwarded-For"
20+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.logLevel: "DEBUG"
21+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.goodBots: ""
22+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.protectRoutes: "/"
23+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.persistentStateFile: "/tmp/state.json"
24+
healthcheck:
25+
test: curl -fs http://localhost/healthz | grep -q OK || exit 1
26+
volumes:
27+
- ./conf/nginx/default.conf:/etc/nginx/conf.d/default.conf:r
28+
networks:
29+
default:
30+
aliases:
31+
- nginx
32+
traefik:
33+
image: traefik:${TRAEFIK_TAG}
34+
command: >-
35+
--api.insecure=true
36+
--api.dashboard=true
37+
--api.debug=true
38+
--ping=true
39+
--entryPoints.http.address=:80
40+
--entryPoints.http.forwardedHeaders.trustedIPs=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
41+
--providers.docker=true
42+
--providers.docker.network=default
43+
--experimental.localPlugins.captcha-protect.moduleName=github.com/libops/captcha-protect
44+
volumes:
45+
- /var/run/docker.sock:/var/run/docker.sock:z
46+
- ./tmp:/tmp:rw
47+
- ./../:/plugins-local/src/github.com/libops/captcha-protect:r
48+
ports:
49+
- "80:80"
50+
- "8080:8080"
51+
mem_limit: 256m
52+
mem_reservation: 128m
53+
networks:
54+
default:
55+
aliases:
56+
- traefik
57+
healthcheck:
58+
test: traefik healthcheck --ping
59+
depends_on:
60+
nginx:
61+
condition: service_healthy
Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
networks:
22
default:
33
services:
4+
redis:
5+
image: redis:7-alpine
6+
ports:
7+
- "6379:6379"
8+
healthcheck:
9+
test: redis-cli ping | grep PONG
10+
interval: 1s
11+
timeout: 3s
12+
retries: 5
13+
networks:
14+
default:
15+
aliases:
16+
- redis
417
nginx:
518
image: nginx:${NGINX_TAG}
619
labels:
@@ -20,7 +33,7 @@ services:
2033
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.logLevel: "DEBUG"
2134
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.goodBots: ""
2235
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.protectRoutes: "/"
23-
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.persistentStateFile: "/tmp/state.json"
36+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.redisAddr: "redis:6379"
2437
healthcheck:
2538
test: curl -fs http://localhost/healthz | grep -q OK || exit 1
2639
volumes:
@@ -29,6 +42,9 @@ services:
2942
default:
3043
aliases:
3144
- nginx
45+
depends_on:
46+
redis:
47+
condition: service_healthy
3248
nginx2:
3349
image: nginx:${NGINX_TAG}
3450
labels:
@@ -48,8 +64,7 @@ services:
4864
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.logLevel: "DEBUG"
4965
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.goodBots: ""
5066
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.protectRoutes: "/"
51-
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.persistentStateFile: "/tmp/state.json"
52-
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.enableStateReconciliation: "true"
67+
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.redisAddr: "redis:6379"
5368
healthcheck:
5469
test: curl -fs http://localhost/healthz | grep -q OK || exit 1
5570
volumes:
@@ -58,6 +73,9 @@ services:
5873
default:
5974
aliases:
6075
- nginx2
76+
depends_on:
77+
redis:
78+
condition: service_healthy
6179
traefik:
6280
image: traefik:${TRAEFIK_TAG}
6381
command: >-
@@ -88,3 +106,5 @@ services:
88106
depends_on:
89107
nginx:
90108
condition: service_healthy
109+
redis:
110+
condition: service_healthy

0 commit comments

Comments
 (0)