Skip to content

Commit f113414

Browse files
Mossakaclaude
andauthored
test: add CI workflow for non-chroot integration tests (#1048)
* feat(api-proxy): add structured logging, metrics, and request tracing - logging.js: structured JSON logging with request IDs (crypto.randomUUID), sanitizeForLog utility, zero external dependencies - metrics.js: in-memory counters (requests_total, bytes), histograms (request_duration_ms with fixed buckets and percentile calculation), gauges (active_requests, uptime), memory-bounded - server.js: replace all console.log/error with structured logger, instrument proxyRequest() with full metrics, add X-Request-ID header propagation, enhance /health with metrics_summary, add GET /metrics endpoint on port 10000 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(api-proxy): add sliding window rate limiter with CLI integration Implement per-provider rate limiting for the API proxy sidecar: - rate-limiter.js: Sliding window counter algorithm with 1-second granularity for RPM/bytes and 1-minute granularity for RPH. Per-provider independence, memory-bounded, fail-open on errors. - server.js: Rate limit check before each proxyRequest() call. Returns 429 with Retry-After, X-RateLimit-* headers and JSON body. Rate limit status added to /health endpoint. - CLI flags: --rate-limit-rpm, --rate-limit-rph, --rate-limit-bytes-pm, --no-rate-limit (all require --enable-api-proxy) - TypeScript: RateLimitConfig interface in types.ts, env var passthrough in docker-manager.ts, validation in cli.ts - Test runner: AwfOptions extended with rate limit fields Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ci: add API proxy unit tests to build workflow Add Jest devDependency and test script to api-proxy package.json, and add a CI step in build.yml to run container-level unit tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add integration tests for api-proxy observability Add two integration test files that verify the observability and rate limiting features work end-to-end with actual Docker containers. api-proxy-observability.test.ts: - /metrics endpoint returns valid JSON with counters, histograms, gauges - /health endpoint includes metrics_summary - X-Request-ID header in proxy responses - Metrics increment after API requests - rate_limits appear in /health api-proxy-rate-limit.test.ts: - 429 response when RPM limit exceeded - Retry-After header in 429 response - X-RateLimit-* headers in 429 response - --no-rate-limit flag disables limiting - Custom RPM reflected in /health - Rate limit metrics in /metrics after rejection Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: extract buildRateLimitConfig and add coverage tests Refactor rate limit validation into a standalone exported function that can be tested independently. Adds 12 unit tests covering defaults, --no-rate-limit, custom values, and validation errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add CI workflow for non-chroot integration tests Add test-integration-suite.yml that runs all 23 non-chroot integration tests in 4 parallel jobs grouped by category: - Domain & Network (7 tests): blocked-domains, dns-servers, empty-domains, wildcard-patterns, ipv6, localhost-access, network-security - Protocol & Security (5 tests): protocol-support, credential-hiding, one-shot-tokens, token-unset, git-operations - Container & Ops (8 tests): container-workdir, docker-warning, environment-variables, error-handling, exit-code-propagation, log-commands, no-docker, volume-mounts - API Proxy (3 tests): api-proxy, api-proxy-observability, api-proxy-rate-limit These tests had no CI pipeline before — only chroot tests ran in CI via test-chroot.yml. Closes #1040 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add missing closing braces in awf-runner.ts merge resolution The merge conflict resolution dropped closing braces for the noRateLimit if-blocks in both run() and runWithSudo() methods, causing TypeScript compilation errors in CI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove duplicate properties in awf-runner.ts from merge Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 01950d5 commit f113414

File tree

2 files changed

+270
-2
lines changed

2 files changed

+270
-2
lines changed
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
name: Integration Tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
workflow_dispatch:
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
test-domain-network:
15+
name: Domain & Network Tests
16+
runs-on: ubuntu-latest
17+
timeout-minutes: 30
18+
19+
steps:
20+
- name: Checkout repository
21+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
22+
23+
- name: Setup Node.js
24+
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
25+
with:
26+
node-version: '22'
27+
cache: 'npm'
28+
29+
- name: Install dependencies
30+
run: npm ci
31+
32+
- name: Build project
33+
run: npm run build
34+
35+
- name: Build local containers
36+
run: |
37+
echo "=== Building local containers ==="
38+
docker build -t ghcr.io/github/gh-aw-firewall/squid:latest containers/squid/
39+
docker build -t ghcr.io/github/gh-aw-firewall/agent:latest containers/agent/
40+
41+
- name: Pre-test cleanup
42+
run: |
43+
echo "=== Pre-test cleanup ==="
44+
./scripts/ci/cleanup.sh || true
45+
46+
- name: Run domain & network tests
47+
run: |
48+
echo "=== Running domain & network tests ==="
49+
npm run test:integration -- \
50+
--testPathPatterns="(blocked-domains|dns-servers|empty-domains|wildcard-patterns|ipv6|localhost-access|network-security)" \
51+
--verbose
52+
env:
53+
JEST_TIMEOUT: 180000
54+
55+
- name: Post-test cleanup
56+
if: always()
57+
run: |
58+
echo "=== Post-test cleanup ==="
59+
./scripts/ci/cleanup.sh || true
60+
61+
- name: Collect logs on failure
62+
if: failure()
63+
run: |
64+
echo "=== Collecting failure logs ==="
65+
docker ps -a || true
66+
docker logs awf-squid 2>&1 || true
67+
docker logs awf-agent 2>&1 || true
68+
ls -la /tmp/awf-* 2>/dev/null || true
69+
sudo cat /tmp/awf-*/squid-logs/access.log 2>/dev/null || true
70+
71+
test-protocol-security:
72+
name: Protocol & Security Tests
73+
runs-on: ubuntu-latest
74+
timeout-minutes: 30
75+
76+
steps:
77+
- name: Checkout repository
78+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
79+
80+
- name: Setup Node.js
81+
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
82+
with:
83+
node-version: '22'
84+
cache: 'npm'
85+
86+
- name: Install dependencies
87+
run: npm ci
88+
89+
- name: Build project
90+
run: npm run build
91+
92+
- name: Build local containers
93+
run: |
94+
echo "=== Building local containers ==="
95+
docker build -t ghcr.io/github/gh-aw-firewall/squid:latest containers/squid/
96+
docker build -t ghcr.io/github/gh-aw-firewall/agent:latest containers/agent/
97+
98+
- name: Pre-test cleanup
99+
run: |
100+
echo "=== Pre-test cleanup ==="
101+
./scripts/ci/cleanup.sh || true
102+
103+
- name: Run protocol & security tests
104+
run: |
105+
echo "=== Running protocol & security tests ==="
106+
npm run test:integration -- \
107+
--testPathPatterns="(protocol-support|credential-hiding|one-shot-tokens|token-unset|git-operations)" \
108+
--verbose
109+
env:
110+
JEST_TIMEOUT: 180000
111+
112+
- name: Post-test cleanup
113+
if: always()
114+
run: |
115+
echo "=== Post-test cleanup ==="
116+
./scripts/ci/cleanup.sh || true
117+
118+
- name: Collect logs on failure
119+
if: failure()
120+
run: |
121+
echo "=== Collecting failure logs ==="
122+
docker ps -a || true
123+
docker logs awf-squid 2>&1 || true
124+
docker logs awf-agent 2>&1 || true
125+
ls -la /tmp/awf-* 2>/dev/null || true
126+
sudo cat /tmp/awf-*/squid-logs/access.log 2>/dev/null || true
127+
128+
test-container-ops:
129+
name: Container & Ops Tests
130+
runs-on: ubuntu-latest
131+
timeout-minutes: 30
132+
133+
steps:
134+
- name: Checkout repository
135+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
136+
137+
- name: Setup Node.js
138+
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
139+
with:
140+
node-version: '22'
141+
cache: 'npm'
142+
143+
- name: Install dependencies
144+
run: npm ci
145+
146+
- name: Build project
147+
run: npm run build
148+
149+
- name: Build local containers
150+
run: |
151+
echo "=== Building local containers ==="
152+
docker build -t ghcr.io/github/gh-aw-firewall/squid:latest containers/squid/
153+
docker build -t ghcr.io/github/gh-aw-firewall/agent:latest containers/agent/
154+
155+
- name: Pre-test cleanup
156+
run: |
157+
echo "=== Pre-test cleanup ==="
158+
./scripts/ci/cleanup.sh || true
159+
160+
- name: Run container & ops tests
161+
run: |
162+
echo "=== Running container & ops tests ==="
163+
npm run test:integration -- \
164+
--testPathPatterns="(container-workdir|docker-warning|environment-variables|error-handling|exit-code-propagation|log-commands|no-docker|volume-mounts)" \
165+
--verbose
166+
env:
167+
JEST_TIMEOUT: 180000
168+
169+
- name: Post-test cleanup
170+
if: always()
171+
run: |
172+
echo "=== Post-test cleanup ==="
173+
./scripts/ci/cleanup.sh || true
174+
175+
- name: Collect logs on failure
176+
if: failure()
177+
run: |
178+
echo "=== Collecting failure logs ==="
179+
docker ps -a || true
180+
docker logs awf-squid 2>&1 || true
181+
docker logs awf-agent 2>&1 || true
182+
ls -la /tmp/awf-* 2>/dev/null || true
183+
sudo cat /tmp/awf-*/squid-logs/access.log 2>/dev/null || true
184+
185+
test-api-proxy:
186+
name: API Proxy Tests
187+
runs-on: ubuntu-latest
188+
timeout-minutes: 30
189+
190+
steps:
191+
- name: Checkout repository
192+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
193+
194+
- name: Setup Node.js
195+
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
196+
with:
197+
node-version: '22'
198+
cache: 'npm'
199+
200+
- name: Install dependencies
201+
run: npm ci
202+
203+
- name: Build project
204+
run: npm run build
205+
206+
- name: Build local containers
207+
run: |
208+
echo "=== Building local containers ==="
209+
docker build -t ghcr.io/github/gh-aw-firewall/squid:latest containers/squid/
210+
docker build -t ghcr.io/github/gh-aw-firewall/agent:latest containers/agent/
211+
212+
- name: Pre-test cleanup
213+
run: |
214+
echo "=== Pre-test cleanup ==="
215+
./scripts/ci/cleanup.sh || true
216+
217+
- name: Run API proxy tests
218+
run: |
219+
echo "=== Running API proxy tests ==="
220+
npm run test:integration -- \
221+
--testPathPatterns="api-proxy" \
222+
--verbose
223+
env:
224+
JEST_TIMEOUT: 180000
225+
226+
- name: Post-test cleanup
227+
if: always()
228+
run: |
229+
echo "=== Post-test cleanup ==="
230+
./scripts/ci/cleanup.sh || true
231+
232+
- name: Collect logs on failure
233+
if: failure()
234+
run: |
235+
echo "=== Collecting failure logs ==="
236+
docker ps -a || true
237+
docker logs awf-squid 2>&1 || true
238+
docker logs awf-agent 2>&1 || true
239+
ls -la /tmp/awf-* 2>/dev/null || true
240+
sudo cat /tmp/awf-*/squid-logs/access.log 2>/dev/null || true

tests/fixtures/awf-runner.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ export interface AwfOptions {
1919
dnsServers?: string[]; // DNS servers to use (e.g., ['8.8.8.8', '2001:4860:4860::8888'])
2020
allowHostPorts?: string; // Ports or port ranges to allow for host access (e.g., '3000' or '3000-8000')
2121
enableApiProxy?: boolean; // Enable API proxy sidecar for LLM credential management
22-
envAll?: boolean; // Pass all host environment variables to container (--env-all)
23-
cliEnv?: Record<string, string>; // Explicit -e KEY=VALUE flags passed to AWF CLI
2422
rateLimitRpm?: number; // Requests per minute per provider
2523
rateLimitRph?: number; // Requests per hour per provider
2624
rateLimitBytesPm?: number; // Request bytes per minute per provider
2725
noRateLimit?: boolean; // Disable rate limiting
26+
envAll?: boolean; // Pass all host environment variables to container (--env-all)
27+
cliEnv?: Record<string, string>; // Explicit -e KEY=VALUE flags passed to AWF CLI
2828
}
2929

3030
export interface AwfResult {
@@ -116,6 +116,20 @@ export class AwfRunner {
116116
args.push('--enable-api-proxy');
117117
}
118118

119+
// Add rate limit flags
120+
if (options.rateLimitRpm !== undefined) {
121+
args.push('--rate-limit-rpm', String(options.rateLimitRpm));
122+
}
123+
if (options.rateLimitRph !== undefined) {
124+
args.push('--rate-limit-rph', String(options.rateLimitRph));
125+
}
126+
if (options.rateLimitBytesPm !== undefined) {
127+
args.push('--rate-limit-bytes-pm', String(options.rateLimitBytesPm));
128+
}
129+
if (options.noRateLimit) {
130+
args.push('--no-rate-limit');
131+
}
132+
119133
// Add --env-all flag
120134
if (options.envAll) {
121135
args.push('--env-all');
@@ -296,6 +310,20 @@ export class AwfRunner {
296310
args.push('--enable-api-proxy');
297311
}
298312

313+
// Add rate limit flags
314+
if (options.rateLimitRpm !== undefined) {
315+
args.push('--rate-limit-rpm', String(options.rateLimitRpm));
316+
}
317+
if (options.rateLimitRph !== undefined) {
318+
args.push('--rate-limit-rph', String(options.rateLimitRph));
319+
}
320+
if (options.rateLimitBytesPm !== undefined) {
321+
args.push('--rate-limit-bytes-pm', String(options.rateLimitBytesPm));
322+
}
323+
if (options.noRateLimit) {
324+
args.push('--no-rate-limit');
325+
}
326+
299327
// Add --env-all flag
300328
if (options.envAll) {
301329
args.push('--env-all');

0 commit comments

Comments
 (0)