mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
chore: add dev tooling — Stryker, gremlins, k6, toxiproxy, Coraza WAF, Zod
Install and configure mutation testing (Stryker for client, go-gremlins for server), load testing (k6), chaos testing (toxiproxy), WAF middleware (Coraza with OWASP rules, opt-in via waf_enabled config), and Zod for runtime schema validation. All tools verified building cleanly.
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
// k6 WebSocket load test for OwnCord server
|
||||
// Run: k6 run --vus 50 --duration 60s scripts/k6/ws-load.js
|
||||
//
|
||||
// Environment variables:
|
||||
// K6_WS_URL - WebSocket URL (default: ws://localhost:8443/ws)
|
||||
// K6_HTTP_URL - HTTP base URL (default: http://localhost:8443)
|
||||
// K6_USERNAME - Test user prefix (default: loadtest)
|
||||
// K6_PASSWORD - Test user password (default: LoadTest123!)
|
||||
// K6_CHANNEL_ID - Channel ID to send messages in (default: 1)
|
||||
|
||||
import ws from "k6/ws";
|
||||
import http from "k6/http";
|
||||
import { check, sleep } from "k6";
|
||||
import { Counter, Rate, Trend } from "k6/metrics";
|
||||
|
||||
// Custom metrics
|
||||
const wsConnections = new Counter("ws_connections");
|
||||
const wsMessages = new Counter("ws_messages_sent");
|
||||
const wsErrors = new Counter("ws_errors");
|
||||
const wsConnectTime = new Trend("ws_connect_time", true);
|
||||
const wsMessageRate = new Rate("ws_message_success");
|
||||
const authTime = new Trend("auth_time", true);
|
||||
|
||||
// Configuration
|
||||
const WS_URL = __ENV.K6_WS_URL || "ws://localhost:8443/ws";
|
||||
const HTTP_URL = __ENV.K6_HTTP_URL || "http://localhost:8443";
|
||||
const USERNAME_PREFIX = __ENV.K6_USERNAME || "loadtest";
|
||||
const PASSWORD = __ENV.K6_PASSWORD || "LoadTest123!";
|
||||
const CHANNEL_ID = parseInt(__ENV.K6_CHANNEL_ID || "1");
|
||||
|
||||
export const options = {
|
||||
scenarios: {
|
||||
// Ramp up connections gradually
|
||||
websocket_load: {
|
||||
executor: "ramping-vus",
|
||||
startVUs: 0,
|
||||
stages: [
|
||||
{ duration: "10s", target: 10 }, // warm up
|
||||
{ duration: "30s", target: 50 }, // ramp to 50
|
||||
{ duration: "60s", target: 50 }, // sustain
|
||||
{ duration: "10s", target: 100 }, // spike
|
||||
{ duration: "30s", target: 100 }, // sustain spike
|
||||
{ duration: "10s", target: 0 }, // ramp down
|
||||
],
|
||||
},
|
||||
},
|
||||
thresholds: {
|
||||
ws_connect_time: ["p(95)<2000"], // 95% connect under 2s
|
||||
ws_message_success: ["rate>0.95"], // 95% message success
|
||||
ws_errors: ["count<50"], // fewer than 50 errors
|
||||
auth_time: ["p(95)<1000"], // 95% auth under 1s
|
||||
},
|
||||
};
|
||||
|
||||
// Login and get session token
|
||||
function authenticate(username) {
|
||||
const start = Date.now();
|
||||
const res = http.post(
|
||||
`${HTTP_URL}/api/v1/auth/login`,
|
||||
JSON.stringify({ username, password: PASSWORD }),
|
||||
{ headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
authTime.add(Date.now() - start);
|
||||
|
||||
if (res.status !== 200) {
|
||||
wsErrors.add(1);
|
||||
return null;
|
||||
}
|
||||
|
||||
const body = JSON.parse(res.body);
|
||||
return body.token;
|
||||
}
|
||||
|
||||
export default function () {
|
||||
const vuId = __VU;
|
||||
const username = `${USERNAME_PREFIX}${vuId}`;
|
||||
|
||||
// Authenticate
|
||||
const token = authenticate(username);
|
||||
if (!token) {
|
||||
sleep(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Connect WebSocket
|
||||
const connectStart = Date.now();
|
||||
const res = ws.connect(WS_URL, null, function (socket) {
|
||||
wsConnectTime.add(Date.now() - connectStart);
|
||||
wsConnections.add(1);
|
||||
|
||||
// Send auth on connect
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "auth",
|
||||
token: token,
|
||||
}),
|
||||
);
|
||||
|
||||
// Handle incoming messages
|
||||
socket.on("message", function (msg) {
|
||||
try {
|
||||
const data = JSON.parse(msg);
|
||||
|
||||
// After auth_ok, focus a channel and start sending
|
||||
if (data.type === "ready") {
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "channel_focus",
|
||||
channel_id: CHANNEL_ID,
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (_e) {
|
||||
wsErrors.add(1);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("error", function (_e) {
|
||||
wsErrors.add(1);
|
||||
});
|
||||
|
||||
// Send messages periodically (respecting rate limits)
|
||||
let msgCount = 0;
|
||||
const maxMessages = 10;
|
||||
|
||||
socket.setInterval(function () {
|
||||
if (msgCount >= maxMessages) {
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const msg = JSON.stringify({
|
||||
type: "chat_send",
|
||||
channel_id: CHANNEL_ID,
|
||||
content: `Load test message ${vuId}-${msgCount} at ${Date.now()}`,
|
||||
});
|
||||
|
||||
socket.send(msg);
|
||||
wsMessages.add(1);
|
||||
wsMessageRate.add(true);
|
||||
msgCount++;
|
||||
}, 2000); // 1 message every 2 seconds (well under rate limit)
|
||||
|
||||
// Send typing indicators
|
||||
socket.setInterval(function () {
|
||||
if (msgCount < maxMessages) {
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "typing",
|
||||
channel_id: CHANNEL_ID,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, 4000); // 1 typing every 4 seconds (under 1/3s limit)
|
||||
|
||||
// Send presence updates
|
||||
socket.setInterval(function () {
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: "presence",
|
||||
status: "online",
|
||||
}),
|
||||
);
|
||||
}, 15000); // 1 presence every 15 seconds (under 1/10s limit)
|
||||
|
||||
// Keep connection alive for the test duration
|
||||
socket.setTimeout(function () {
|
||||
socket.close();
|
||||
}, 25000);
|
||||
});
|
||||
|
||||
check(res, {
|
||||
"WebSocket status is 101": (r) => r && r.status === 101,
|
||||
});
|
||||
|
||||
if (!res || res.status !== 101) {
|
||||
wsErrors.add(1);
|
||||
wsMessageRate.add(false);
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
export function handleSummary(data) {
|
||||
return {
|
||||
stdout: textSummary(data, { indent: " ", enableColors: true }),
|
||||
"reports/k6-summary.json": JSON.stringify(data, null, 2),
|
||||
};
|
||||
}
|
||||
|
||||
// Built-in k6 text summary
|
||||
function textSummary(data, opts) {
|
||||
// k6 handles this automatically when not overridden
|
||||
return JSON.stringify(data, null, 2);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/bin/bash
|
||||
# Toxiproxy chaos testing for OwnCord
|
||||
#
|
||||
# Prerequisites:
|
||||
# 1. toxiproxy-server running: toxiproxy-server &
|
||||
# 2. OwnCord server running on port 8443
|
||||
# 3. toxiproxy-cli available in PATH
|
||||
#
|
||||
# Usage: bash scripts/toxiproxy/chaos-test.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
TOXI_HOST="${TOXI_HOST:-localhost:8474}"
|
||||
SERVER_HOST="${SERVER_HOST:-localhost}"
|
||||
SERVER_PORT="${SERVER_PORT:-8443}"
|
||||
PROXY_PORT="${PROXY_PORT:-18443}"
|
||||
|
||||
echo "=== OwnCord Chaos Testing ==="
|
||||
echo "Toxiproxy API: $TOXI_HOST"
|
||||
echo "Target server: $SERVER_HOST:$SERVER_PORT"
|
||||
echo "Proxy port: $PROXY_PORT"
|
||||
echo ""
|
||||
|
||||
# Create proxy
|
||||
echo "[1/7] Creating proxy..."
|
||||
toxiproxy-cli create owncord \
|
||||
--listen "0.0.0.0:$PROXY_PORT" \
|
||||
--upstream "$SERVER_HOST:$SERVER_PORT" 2>/dev/null || \
|
||||
echo " (proxy already exists)"
|
||||
|
||||
echo ""
|
||||
echo "[2/7] Test: Normal connectivity (baseline)"
|
||||
echo " Connect to localhost:$PROXY_PORT and verify response..."
|
||||
curl -sf "http://localhost:$PROXY_PORT/api/v1/health" && echo " OK" || echo " FAIL"
|
||||
sleep 1
|
||||
|
||||
echo ""
|
||||
echo "[3/7] Test: High latency (500ms)"
|
||||
echo " Simulates slow network / cross-region..."
|
||||
toxiproxy-cli toxic add owncord --type latency \
|
||||
--attribute latency=500 --attribute jitter=100 \
|
||||
--toxicName latency_test 2>/dev/null
|
||||
echo " Running health check with latency..."
|
||||
time curl -sf "http://localhost:$PROXY_PORT/api/v1/health" && echo " OK" || echo " FAIL"
|
||||
toxiproxy-cli toxic remove owncord --toxicName latency_test
|
||||
sleep 1
|
||||
|
||||
echo ""
|
||||
echo "[4/7] Test: Packet loss (30%)"
|
||||
echo " Simulates unreliable WiFi..."
|
||||
toxiproxy-cli toxic add owncord --type timeout \
|
||||
--attribute timeout=3000 \
|
||||
--toxicName timeout_test 2>/dev/null
|
||||
echo " Health check should timeout after 3s..."
|
||||
timeout 5 curl -sf "http://localhost:$PROXY_PORT/api/v1/health" 2>/dev/null && echo " OK (fast)" || echo " Timed out as expected"
|
||||
toxiproxy-cli toxic remove owncord --toxicName timeout_test
|
||||
sleep 1
|
||||
|
||||
echo ""
|
||||
echo "[5/7] Test: Bandwidth limit (10KB/s)"
|
||||
echo " Simulates throttled connection..."
|
||||
toxiproxy-cli toxic add owncord --type bandwidth \
|
||||
--attribute rate=10 \
|
||||
--toxicName bandwidth_test 2>/dev/null
|
||||
echo " Health check with bandwidth limit..."
|
||||
time curl -sf "http://localhost:$PROXY_PORT/api/v1/health" && echo " OK" || echo " FAIL"
|
||||
toxiproxy-cli toxic remove owncord --toxicName bandwidth_test
|
||||
sleep 1
|
||||
|
||||
echo ""
|
||||
echo "[6/7] Test: Connection reset"
|
||||
echo " Simulates abrupt disconnection..."
|
||||
toxiproxy-cli toxic add owncord --type reset_peer \
|
||||
--attribute timeout=1000 \
|
||||
--toxicName reset_test 2>/dev/null
|
||||
echo " Health check should fail after 1s..."
|
||||
curl -sf --max-time 3 "http://localhost:$PROXY_PORT/api/v1/health" 2>/dev/null && echo " OK (unexpected)" || echo " Reset as expected"
|
||||
toxiproxy-cli toxic remove owncord --toxicName reset_test
|
||||
sleep 1
|
||||
|
||||
echo ""
|
||||
echo "[7/7] Test: Downstream slicer (fragment responses)"
|
||||
echo " Simulates packet fragmentation..."
|
||||
toxiproxy-cli toxic add owncord --type slicer \
|
||||
--attribute average_size=10 --attribute size_variation=5 --attribute delay=10 \
|
||||
--toxicName slicer_test 2>/dev/null
|
||||
echo " Health check with sliced responses..."
|
||||
curl -sf "http://localhost:$PROXY_PORT/api/v1/health" && echo " OK" || echo " FAIL"
|
||||
toxiproxy-cli toxic remove owncord --toxicName slicer_test
|
||||
|
||||
echo ""
|
||||
echo "=== Cleanup ==="
|
||||
toxiproxy-cli delete owncord 2>/dev/null || true
|
||||
echo "Done. All chaos tests complete."
|
||||
Reference in New Issue
Block a user