diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 023c41de..89d59e66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -178,6 +178,49 @@ jobs: path: dist/ retention-days: 1 + load-test: + name: Load Testing + needs: build + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: 20 + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Install k6 + run: | + sudo apt-get update + sudo apt-get install -y gnupg2 software-properties-common + sudo wget -q -O - https://dl.k6.io/key.gpg | sudo apt-key add - + echo "deb https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list + sudo apt-get update + sudo apt-get install -y k6 + + - name: Start API in background + run: | + npm run start & + sleep 10 + + - name: Run load tests + run: npm run loadtest:ci + env: + API_URL: http://localhost:3000 + NODE_ENV: test + + - name: Upload k6 results + uses: actions/upload-artifact@v4 + with: + name: k6-results + path: artifacts/k6-results.json + deploy-staging: name: Deploy to Staging needs: [build, test-integration, test-e2e, test-security] diff --git a/docs/LOAD_TESTING.md b/docs/LOAD_TESTING.md new file mode 100644 index 00000000..4e404f5e --- /dev/null +++ b/docs/LOAD_TESTING.md @@ -0,0 +1,44 @@ +# Load Testing Guide + +This repository now includes a dedicated load testing suite to satisfy #140. + +## Tools +- k6 (https://k6.io) + +## Location +- `loadtests/propchain-loadtest.js` + +## Running locally +1. Install dependencies: + ```bash + npm ci + npm install --save-dev k6 + ``` +2. Set env vars: + ```bash + export API_URL=http://localhost:3000 + export TEST_USER_EMAIL=loadtest@propchain.local + export TEST_USER_PASSWORD=Password123! + ``` +3. Start the backend in a separate terminal: + ```bash + npm run start + ``` +4. Run load test: + ```bash + npm run loadtest + ``` + +## CI integration +- Workflow: `.github/workflows/ci.yml` +- Job: `load-test` +- Command: `npm run loadtest:ci` +- Results are saved as artifact `k6-results` in JSON format. + +## Performance criteria +- http_req_failed: < 1% +- 95th percentile response time: < 600ms + +## To add scenarios +- Edit `loadtests/propchain-loadtest.js` with more groups/endpoints. +- Add custom thresholds for endpoint-level behavior. diff --git a/loadtests/propchain-loadtest.js b/loadtests/propchain-loadtest.js new file mode 100644 index 00000000..67b6c3a9 --- /dev/null +++ b/loadtests/propchain-loadtest.js @@ -0,0 +1,62 @@ +import http from 'k6/http'; +import { check, group, sleep } from 'k6'; + +export const options = { + vus: Number(__ENV.LOAD_VUS || 40), + duration: __ENV.LOAD_DURATION || '3m', + thresholds: { + http_req_failed: ['rate<0.01'], + http_req_duration: ['p(95)<600'], + }, +}; + +const API_URL = __ENV.API_URL || 'http://localhost:3000'; +const TEST_EMAIL = __ENV.TEST_USER_EMAIL || 'loadtest@propchain.local'; +const TEST_PASSWORD = __ENV.TEST_USER_PASSWORD || 'Password123!'; + +function login() { + const payload = JSON.stringify({ + email: TEST_EMAIL, + password: TEST_PASSWORD, + }); + + const headers = { + 'Content-Type': 'application/json', + }; + + const res = http.post(`${API_URL}/auth/login`, payload, { headers }); + + check(res, { + 'login status is 200': (r) => r.status === 200, + 'login returns access token': (r) => r.json('accessToken') !== undefined, + }); + + return res.json('accessToken'); +} + +export default () => { + group('Auth + Basic API traffic', () => { + const token = login(); + const authHeaders = { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }; + + const props = http.get(`${API_URL}/properties`, { headers: authHeaders }); + check(props, { + 'properties status is 200': (r) => r.status === 200, + }); + + const docs = http.get(`${API_URL}/documents`, { headers: authHeaders }); + check(docs, { + 'documents status is 200': (r) => r.status === 200, + }); + + const health = http.get(`${API_URL}/health`); + check(health, { + 'health status is 200': (r) => r.status === 200, + }); + + sleep(1); + }); +}; diff --git a/package.json b/package.json index 5881b995..7eab6740 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,8 @@ "db:backup": "bash scripts/backup.sh", "db:restore": "bash scripts/restore.sh", "db:benchmark": "ts-node test/database/performance.benchmark.ts", + "loadtest": "npx k6 run loadtests/propchain-loadtest.js", + "loadtest:ci": "npx k6 run --out json=artifacts/k6-results.json loadtests/propchain-loadtest.js", "docs:generate": "typedoc --skipErrorChecking" }, "dependencies": {