From 01a17adc764ddbd22133692d7df0f60a22ff0600 Mon Sep 17 00:00:00 2001 From: Arun Date: Tue, 26 Aug 2025 23:56:37 -0400 Subject: [PATCH 01/18] feat(testing): add comprehensive testing workflows - Performance Testing: K6 load, stress, spike testing + Artillery - E2E Testing: Multi-browser Playwright tests with visual regression - Contract Testing: Consumer-driven contracts with Pact - Chaos Engineering: Network, resource, database, service chaos testing - API Testing: Postman/Newman collections + security tests - Smoke Testing: Quick critical path validation (< 5 min) - Mutation Testing: Stryker for test quality validation Each workflow includes: - Automated setup and teardown - Comprehensive reporting - Artifact uploads - GitHub summary generation - Error handling and retries --- .github/workflows/api-testing.yml | 558 ++++++++++++++++++++++ .github/workflows/chaos-engineering.yml | 380 +++++++++++++++ .github/workflows/contract-testing.yml | 405 ++++++++++++++++ .github/workflows/e2e-testing.yml | 380 +++++++++++++++ .github/workflows/mutation-testing.yml | 270 +++++++++++ .github/workflows/performance-testing.yml | 442 +++++++++++++++++ .github/workflows/smoke-testing.yml | 170 +++++++ 7 files changed, 2605 insertions(+) create mode 100644 .github/workflows/api-testing.yml create mode 100644 .github/workflows/chaos-engineering.yml create mode 100644 .github/workflows/contract-testing.yml create mode 100644 .github/workflows/e2e-testing.yml create mode 100644 .github/workflows/mutation-testing.yml create mode 100644 .github/workflows/performance-testing.yml create mode 100644 .github/workflows/smoke-testing.yml diff --git a/.github/workflows/api-testing.yml b/.github/workflows/api-testing.yml new file mode 100644 index 0000000..7ed0ef8 --- /dev/null +++ b/.github/workflows/api-testing.yml @@ -0,0 +1,558 @@ +name: API Testing + +on: + pull_request: + branches: [main, develop] + paths: + - "backend/**" + - ".github/workflows/api-testing.yml" + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + actions: read + checks: write + +jobs: + setup-api-testing: + name: Setup API Testing Environment + runs-on: ubuntu-latest + outputs: + api_url: ${{ steps.setup.outputs.api_url }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Start backend services + run: | + docker compose up -d backend postgres redis + + # Wait for backend to be ready + echo "Waiting for backend service..." + timeout 300 bash -c 'until curl -f http://localhost:3001/api/health; do sleep 5; done' + echo "βœ… Backend is ready" + + - name: Set API URL + id: setup + run: echo "api_url=http://localhost:3001/api" >> $GITHUB_OUTPUT + + postman-newman-tests: + name: Postman/Newman API Tests + runs-on: ubuntu-latest + needs: setup-api-testing + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: Install Newman + run: npm install -g newman newman-reporter-htmlextra newman-reporter-json-summary + + - name: Create Postman collection + run: | + mkdir -p tests/api + cat > tests/api/connectkit-api.postman_collection.json << 'EOF' + { + "info": { + "name": "ConnectKit API Tests", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Health Check", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', () => {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test('Response time is less than 500ms', () => {", + " pm.expect(pm.response.responseTime).to.be.below(500);", + "});", + "", + "pm.test('Health check returns status', () => {", + " const response = pm.response.json();", + " pm.expect(response).to.have.property('status');", + " pm.expect(response.status).to.equal('healthy');", + "});" + ] + } + } + ], + "request": { + "method": "GET", + "url": "{{API_URL}}/health" + } + }, + { + "name": "Authentication", + "item": [ + { + "name": "Register User", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const randomEmail = `test${Date.now()}@example.com`;", + "pm.collectionVariables.set('testEmail', randomEmail);" + ] + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test('User registration successful', () => {", + " pm.response.to.have.status(201);", + "});", + "", + "pm.test('Response contains user data', () => {", + " const response = pm.response.json();", + " pm.expect(response).to.have.property('user');", + " pm.expect(response.user).to.have.property('email');", + "});" + ] + } + } + ], + "request": { + "method": "POST", + "url": "{{API_URL}}/auth/register", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\\n \\"email\\": \\"{{testEmail}}\\",\\n \\"password\\": \\"TestPassword123!\\",\\n \\"name\\": \\"Test User\\"\\n}" + } + } + }, + { + "name": "Login User", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Login successful', () => {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test('Response contains token', () => {", + " const response = pm.response.json();", + " pm.expect(response).to.have.property('token');", + " pm.collectionVariables.set('authToken', response.token);", + "});", + "", + "pm.test('Token is valid JWT', () => {", + " const response = pm.response.json();", + " const tokenParts = response.token.split('.');", + " pm.expect(tokenParts).to.have.lengthOf(3);", + "});" + ] + } + } + ], + "request": { + "method": "POST", + "url": "{{API_URL}}/auth/login", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\\n \\"email\\": \\"{{testEmail}}\\",\\n \\"password\\": \\"TestPassword123!\\"\\n}" + } + } + } + ] + }, + { + "name": "Contacts", + "item": [ + { + "name": "Create Contact", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Contact created successfully', () => {", + " pm.response.to.have.status(201);", + "});", + "", + "pm.test('Response contains contact ID', () => {", + " const response = pm.response.json();", + " pm.expect(response).to.have.property('id');", + " pm.collectionVariables.set('contactId', response.id);", + "});" + ] + } + } + ], + "request": { + "method": "POST", + "url": "{{API_URL}}/contacts", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\\n \\"firstName\\": \\"John\\",\\n \\"lastName\\": \\"Doe\\",\\n \\"email\\": \\"john.doe@example.com\\",\\n \\"phone\\": \\"+1234567890\\"\\n}" + } + } + }, + { + "name": "Get Contacts List", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Contacts retrieved successfully', () => {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test('Response is paginated', () => {", + " const response = pm.response.json();", + " pm.expect(response).to.have.property('data');", + " pm.expect(response).to.have.property('pagination');", + " pm.expect(response.data).to.be.an('array');", + "});" + ] + } + } + ], + "request": { + "method": "GET", + "url": "{{API_URL}}/contacts", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}" + } + ] + } + }, + { + "name": "Update Contact", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Contact updated successfully', () => {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test('Updated fields are correct', () => {", + " const response = pm.response.json();", + " pm.expect(response.firstName).to.equal('Jane');", + "});" + ] + } + } + ], + "request": { + "method": "PUT", + "url": "{{API_URL}}/contacts/{{contactId}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\\n \\"firstName\\": \\"Jane\\"\\n}" + } + } + }, + { + "name": "Delete Contact", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Contact deleted successfully', () => {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ], + "request": { + "method": "DELETE", + "url": "{{API_URL}}/contacts/{{contactId}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}" + } + ] + } + } + ] + } + ], + "variable": [ + { + "key": "API_URL", + "value": "http://localhost:3001/api", + "type": "string" + } + ] + } + EOF + + - name: Run Newman tests + run: | + newman run tests/api/connectkit-api.postman_collection.json \ + --environment-var API_URL=${{ needs.setup-api-testing.outputs.api_url }} \ + --reporters cli,json,htmlextra \ + --reporter-json-export results/newman-results.json \ + --reporter-htmlextra-export results/newman-report.html + continue-on-error: true + + - name: Upload Newman results + if: always() + uses: actions/upload-artifact@v4 + with: + name: newman-results-${{ github.run_number }} + path: results/ + retention-days: 30 + + rest-assured-tests: + name: REST Assured API Tests + runs-on: ubuntu-latest + needs: setup-api-testing + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Create REST Assured test script + run: | + mkdir -p tests/api + cat > tests/api/rest-assured-test.js << 'EOF' + const axios = require('axios'); + const assert = require('assert'); + + const API_URL = process.env.API_URL || 'http://localhost:3001/api'; + + async function runTests() { + console.log('πŸ§ͺ Running REST Assured style API tests...\n'); + + // Test 1: Health Check + console.log('Test 1: Health Check'); + const healthRes = await axios.get(`${API_URL}/health`); + assert.strictEqual(healthRes.status, 200, 'Health check should return 200'); + assert.strictEqual(healthRes.data.status, 'healthy', 'Status should be healthy'); + console.log('βœ… Health check passed\n'); + + // Test 2: Authentication Flow + console.log('Test 2: Authentication Flow'); + const email = `test${Date.now()}@example.com`; + + // Register + const registerRes = await axios.post(`${API_URL}/auth/register`, { + email, + password: 'TestPassword123!', + name: 'Test User' + }); + assert.strictEqual(registerRes.status, 201, 'Registration should return 201'); + console.log('βœ… Registration passed'); + + // Login + const loginRes = await axios.post(`${API_URL}/auth/login`, { + email, + password: 'TestPassword123!' + }); + assert.strictEqual(loginRes.status, 200, 'Login should return 200'); + assert(loginRes.data.token, 'Login should return token'); + const token = loginRes.data.token; + console.log('βœ… Login passed\n'); + + // Test 3: Contact CRUD + console.log('Test 3: Contact CRUD Operations'); + + // Create + const createRes = await axios.post(`${API_URL}/contacts`, { + firstName: 'John', + lastName: 'Doe', + email: 'john@example.com', + phone: '+1234567890' + }, { + headers: { 'Authorization': `Bearer ${token}` } + }); + assert.strictEqual(createRes.status, 201, 'Create contact should return 201'); + const contactId = createRes.data.id; + console.log('βœ… Create contact passed'); + + // Read + const getRes = await axios.get(`${API_URL}/contacts/${contactId}`, { + headers: { 'Authorization': `Bearer ${token}` } + }); + assert.strictEqual(getRes.status, 200, 'Get contact should return 200'); + console.log('βœ… Get contact passed'); + + // Update + const updateRes = await axios.put(`${API_URL}/contacts/${contactId}`, { + firstName: 'Jane' + }, { + headers: { 'Authorization': `Bearer ${token}` } + }); + assert.strictEqual(updateRes.status, 200, 'Update contact should return 200'); + console.log('βœ… Update contact passed'); + + // Delete + const deleteRes = await axios.delete(`${API_URL}/contacts/${contactId}`, { + headers: { 'Authorization': `Bearer ${token}` } + }); + assert.strictEqual(deleteRes.status, 204, 'Delete contact should return 204'); + console.log('βœ… Delete contact passed\n'); + + console.log('πŸŽ‰ All REST Assured tests passed!'); + } + + runTests().catch(error => { + console.error('❌ Test failed:', error.message); + process.exit(1); + }); + EOF + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: Install dependencies + run: npm install axios + + - name: Run REST Assured style tests + run: node tests/api/rest-assured-test.js + env: + API_URL: ${{ needs.setup-api-testing.outputs.api_url }} + continue-on-error: true + + api-security-tests: + name: API Security Testing + runs-on: ubuntu-latest + needs: setup-api-testing + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run API security tests + run: | + echo "## πŸ”’ API Security Tests" >> $GITHUB_STEP_SUMMARY + API_URL="${{ needs.setup-api-testing.outputs.api_url }}" + + # Test 1: SQL Injection + echo "### SQL Injection Test" >> $GITHUB_STEP_SUMMARY + RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$API_URL/contacts?search=' OR '1'='1") + if [ "$RESPONSE" = "400" ] || [ "$RESPONSE" = "401" ]; then + echo "βœ… Protected against SQL injection" >> $GITHUB_STEP_SUMMARY + else + echo "⚠️ Potential SQL injection vulnerability (status: $RESPONSE)" >> $GITHUB_STEP_SUMMARY + fi + + # Test 2: XSS Prevention + echo "### XSS Prevention Test" >> $GITHUB_STEP_SUMMARY + XSS_PAYLOAD='' + RESPONSE=$(curl -s -X POST "$API_URL/auth/register" \ + -H "Content-Type: application/json" \ + -d "{\"email\":\"$XSS_PAYLOAD\",\"password\":\"test\"}") + if echo "$RESPONSE" | grep -q "