|
| 1 | +# Integration Tests |
| 2 | + |
| 3 | +Recording mParticle Web SDK API requests using WireMock for integration testing. |
| 4 | + |
| 5 | +## Prerequisites |
| 6 | + |
| 7 | +Before getting started, ensure you have the following installed: |
| 8 | + |
| 9 | +- **Node.js** (v18 or higher) |
| 10 | +- **Docker** (for running WireMock locally) |
| 11 | +- **Python 3** (for mapping transformation scripts) |
| 12 | + |
| 13 | +### Install Dependencies |
| 14 | + |
| 15 | +```bash |
| 16 | +cd IntegrationTests |
| 17 | +npm install |
| 18 | +``` |
| 19 | + |
| 20 | +This will install: |
| 21 | +- **Playwright** - Headless browser for running tests |
| 22 | +- **http-server** - Local HTTP server for serving the test app |
| 23 | + |
| 24 | +### Install Playwright Browsers |
| 25 | + |
| 26 | +```bash |
| 27 | +npx playwright install chromium |
| 28 | +``` |
| 29 | + |
| 30 | +## Overview |
| 31 | + |
| 32 | +This project provides tools for integration testing the mParticle Web SDK by: |
| 33 | +- Building the mParticle Web SDK from source |
| 34 | +- Running a minimal test web application in a headless browser |
| 35 | +- Recording/verifying all API traffic with WireMock |
| 36 | + |
| 37 | +The framework supports two modes: |
| 38 | +1. **Recording Mode** - Captures SDK network requests to create baseline mappings |
| 39 | +2. **Verification Mode** - Validates SDK requests against stored baselines |
| 40 | + |
| 41 | +## Directory Structure |
| 42 | + |
| 43 | +``` |
| 44 | +IntegrationTests/ |
| 45 | +├── common.sh # Shared shell functions |
| 46 | +├── run_wiremock_recorder.sh # Recording mode script |
| 47 | +├── run_clean_integration_tests.sh # Verification mode script |
| 48 | +├── run-browser-test.js # Playwright browser test runner |
| 49 | +├── sanitize_mapping.py # API key sanitization script |
| 50 | +├── transform_mapping_body.py # Request body transformation script |
| 51 | +├── package.json # Node.js dependencies |
| 52 | +├── README.md # This file |
| 53 | +├── test-app/ # Test web application |
| 54 | +│ ├── index.html # HTML page |
| 55 | +│ ├── test-app.js # Test logic |
| 56 | +│ └── test-config.js # WireMock endpoint configuration |
| 57 | +└── wiremock-recordings/ # Baseline mappings |
| 58 | + ├── mappings/ # Request/response mapping files |
| 59 | + └── __files/ # Response body files |
| 60 | +``` |
| 61 | + |
| 62 | +## Available Scripts |
| 63 | + |
| 64 | +### `run_wiremock_recorder.sh` - Record API Requests |
| 65 | + |
| 66 | +Records all mParticle SDK API requests using WireMock for later use in integration testing. |
| 67 | + |
| 68 | +```bash |
| 69 | +./run_wiremock_recorder.sh |
| 70 | +``` |
| 71 | + |
| 72 | +**What it does:** |
| 73 | +1. Builds mParticle Web SDK from source (`npm run build`) |
| 74 | +2. Installs Playwright and dependencies |
| 75 | +3. Starts WireMock container in recording mode |
| 76 | +4. Starts local HTTP server for test app |
| 77 | +5. Runs test app in headless Chromium browser |
| 78 | +6. Records all API traffic to mapping files |
| 79 | +7. Displays recorded files |
| 80 | + |
| 81 | +**Recorded Files:** |
| 82 | +- `wiremock-recordings/mappings/*.json` - API request/response mappings |
| 83 | +- `wiremock-recordings/__files/*` - Response body files |
| 84 | + |
| 85 | +### `run_clean_integration_tests.sh` - Verify Against Baselines |
| 86 | + |
| 87 | +Runs the test app and verifies all requests match the stored baseline mappings. |
| 88 | + |
| 89 | +```bash |
| 90 | +./run_clean_integration_tests.sh |
| 91 | +``` |
| 92 | + |
| 93 | +**What it does:** |
| 94 | +1. Builds mParticle Web SDK from source |
| 95 | +2. Escapes mapping bodies for WireMock compatibility |
| 96 | +3. Starts WireMock in verification mode with stored mappings |
| 97 | +4. Runs test app in headless browser |
| 98 | +5. Verifies all requests matched mappings |
| 99 | +6. Verifies all mappings were invoked |
| 100 | +7. Returns exit code 1 on any verification failure |
| 101 | + |
| 102 | +**Exit Codes:** |
| 103 | +- `0` - All tests passed |
| 104 | +- `1` - Verification failed (unmatched requests or unused mappings) |
| 105 | + |
| 106 | +### `sanitize_mapping.py` - Remove API Keys |
| 107 | + |
| 108 | +Sanitizes WireMock mapping files by replacing API keys with regex patterns. |
| 109 | + |
| 110 | +```bash |
| 111 | +python3 sanitize_mapping.py <mapping_file> --test-name <name> |
| 112 | +``` |
| 113 | + |
| 114 | +**Example:** |
| 115 | +```bash |
| 116 | +python3 sanitize_mapping.py \ |
| 117 | + wiremock-recordings/mappings/mapping-v1-us1-abc123-identify.json \ |
| 118 | + --test-name identify |
| 119 | +``` |
| 120 | + |
| 121 | +**What it does:** |
| 122 | +- Replaces API keys in URLs with regex pattern `us1-[a-f0-9]+` |
| 123 | +- Renames mapping file to `mapping-{test-name}.json` |
| 124 | +- Renames body file to `body-{test-name}.json` |
| 125 | +- Updates all references in the mapping JSON |
| 126 | + |
| 127 | +### `transform_mapping_body.py` - Transform Request Bodies |
| 128 | + |
| 129 | +Transforms request body JSON in WireMock mappings. |
| 130 | + |
| 131 | +```bash |
| 132 | +# Display as readable JSON |
| 133 | +python3 transform_mapping_body.py <mapping_file> unescape |
| 134 | + |
| 135 | +# Convert back to escaped string (WireMock format) |
| 136 | +python3 transform_mapping_body.py <mapping_file> escape |
| 137 | + |
| 138 | +# Replace dynamic fields and save |
| 139 | +python3 transform_mapping_body.py <mapping_file> unescape+update |
| 140 | +``` |
| 141 | + |
| 142 | +**Modes:** |
| 143 | +- `unescape` - Convert escaped string to formatted JSON object |
| 144 | +- `escape` - Convert JSON object back to escaped string |
| 145 | +- `unescape+update` - Parse, replace dynamic fields with `${json-unit.ignore}`, and save |
| 146 | + |
| 147 | +**Dynamic fields replaced:** |
| 148 | +- `session_id`, `timestamp_unixtime_ms`, `ct`, `est`, `sct` |
| 149 | +- `id`, `mpid`, `das`, `sdk_version`, `client_generated_id` |
| 150 | +- And more (see script for full list) |
| 151 | + |
| 152 | +## Development Workflow |
| 153 | + |
| 154 | +### Initial Recording of API Requests |
| 155 | + |
| 156 | +⚠️ **Important**: Recording requires a **real mParticle API key** and **Docker**. |
| 157 | + |
| 158 | +#### Step 1: Configure Real API Key (Temporarily) |
| 159 | + |
| 160 | +Edit `test-app/test-config.js` and replace the placeholder with your real API key: |
| 161 | + |
| 162 | +```javascript |
| 163 | +// TEMPORARILY set your real API key for recording |
| 164 | +API_KEY: 'us1-YOUR_REAL_API_KEY_HERE', // Replace with actual key |
| 165 | +``` |
| 166 | + |
| 167 | +#### Step 2: Run the WireMock Recorder |
| 168 | + |
| 169 | +From your **local terminal** (not Cursor terminal, to access Docker): |
| 170 | + |
| 171 | +```bash |
| 172 | +cd IntegrationTests |
| 173 | +./run_wiremock_recorder.sh |
| 174 | +``` |
| 175 | + |
| 176 | +This will: |
| 177 | +- Build the SDK |
| 178 | +- Start WireMock in recording mode |
| 179 | +- Run all tests in a headless browser |
| 180 | +- Save recordings to `wiremock-recordings/mappings/` |
| 181 | + |
| 182 | +#### Step 3: Process the Recordings |
| 183 | + |
| 184 | +Sanitize and transform each recorded mapping: |
| 185 | + |
| 186 | +```bash |
| 187 | +# Sanitize API keys and rename |
| 188 | +python3 sanitize_mapping.py \ |
| 189 | + wiremock-recordings/mappings/mapping-xxx.json \ |
| 190 | + --test-name identify |
| 191 | + |
| 192 | +# Transform request bodies |
| 193 | +python3 transform_mapping_body.py \ |
| 194 | + wiremock-recordings/mappings/mapping-identify.json \ |
| 195 | + unescape+update |
| 196 | +``` |
| 197 | + |
| 198 | +#### Step 4: Revert API Key to Placeholder |
| 199 | + |
| 200 | +**Critical**: After recording, restore the placeholder in `test-config.js`: |
| 201 | + |
| 202 | +```javascript |
| 203 | +API_KEY: 'us1-test0000000000000000000000000', // Placeholder restored |
| 204 | +``` |
| 205 | + |
| 206 | +#### Step 5: Verify and Commit |
| 207 | + |
| 208 | +```bash |
| 209 | +# Test with placeholder key |
| 210 | +./run_clean_integration_tests.sh |
| 211 | + |
| 212 | +# If tests pass, commit |
| 213 | +git add wiremock-recordings/ test-app/test-config.js |
| 214 | +git commit -m "Add sanitized baseline mappings" |
| 215 | +``` |
| 216 | + |
| 217 | +### Running Integration Tests |
| 218 | + |
| 219 | +```bash |
| 220 | +./run_clean_integration_tests.sh |
| 221 | +``` |
| 222 | + |
| 223 | +The script will: |
| 224 | +1. Rebuild the SDK with latest changes |
| 225 | +2. Run tests against stored baselines |
| 226 | +3. Report any mismatches |
| 227 | +4. Exit with code 1 if verification fails |
| 228 | + |
| 229 | +## Test App Configuration |
| 230 | + |
| 231 | +The test app is configured via `test-app/test-config.js`: |
| 232 | + |
| 233 | +```javascript |
| 234 | +const TEST_CONFIG = { |
| 235 | + WIREMOCK_HOST: '127.0.0.1', |
| 236 | + WIREMOCK_HTTPS_PORT: '8443', |
| 237 | + API_KEY: 'us1-test0000000000000000000000000', |
| 238 | + API_SECRET: 'test-secret-key', |
| 239 | + // ... |
| 240 | +}; |
| 241 | +``` |
| 242 | + |
| 243 | +This configuration: |
| 244 | +- Points all SDK endpoints to local WireMock (`127.0.0.1:8443`) |
| 245 | +- Uses a test API key (will be sanitized in recordings) |
| 246 | +- Works identically in local development and CI environments |
| 247 | + |
| 248 | +## Adding New Tests |
| 249 | + |
| 250 | +1. **Add test function** in `test-app/test-app.js`: |
| 251 | + ```javascript |
| 252 | + async function testMyNewFeature() { |
| 253 | + log('Starting Test: My New Feature'); |
| 254 | + // Call SDK methods |
| 255 | + mParticle.logEvent('Test Event', mParticle.EventType.Other); |
| 256 | + await waitForUpload(); |
| 257 | + log('Test complete', 'success'); |
| 258 | + } |
| 259 | + ``` |
| 260 | + |
| 261 | +2. **Call the test** in `runTests()`: |
| 262 | + ```javascript |
| 263 | + async function runTests() { |
| 264 | + // ... existing tests |
| 265 | + await testMyNewFeature(); |
| 266 | + } |
| 267 | + ``` |
| 268 | + |
| 269 | +3. **Record new baselines:** |
| 270 | + ```bash |
| 271 | + ./run_wiremock_recorder.sh |
| 272 | + ``` |
| 273 | + |
| 274 | +4. **Sanitize and commit** the new mappings. |
| 275 | + |
| 276 | +## Current Test Coverage |
| 277 | + |
| 278 | +| Test | Description | Status | |
| 279 | +|------|-------------|--------| |
| 280 | +| Identity | SDK initialization with identify call | Implemented | |
| 281 | +| Simple Event | `logEvent()` with basic attributes | Implemented | |
| 282 | +| Event with Attributes & Flags | `logEvent()` with custom flags | Implemented | |
| 283 | +| Screen View | `logPageView()` | Implemented | |
| 284 | +| Commerce Event | `eCommerce.logProductAction()` purchase | Implemented | |
| 285 | + |
| 286 | +### Test Details |
| 287 | + |
| 288 | +1. **Identity** (`testIdentify`) |
| 289 | + - Initializes SDK with `identifyRequest` |
| 290 | + - Verifies MPID is returned |
| 291 | + - Sets user attribute |
| 292 | + |
| 293 | +2. **Simple Event** (`testSimpleEvent`) |
| 294 | + - Logs `"Simple Event Name"` event |
| 295 | + - Includes `{ SimpleKey: "SimpleValue" }` attributes |
| 296 | + |
| 297 | +3. **Event with Attributes & Flags** (`testEventWithCustomAttributesAndFlags`) |
| 298 | + - Logs `"Event With Attributes"` event |
| 299 | + - Includes string, number, boolean, date attributes |
| 300 | + - Includes custom flags (not forwarded to providers) |
| 301 | + |
| 302 | +4. **Screen View** (`testLogScreen`) |
| 303 | + - Logs `"Home Screen"` page view |
| 304 | + - Includes page category and referrer |
| 305 | + |
| 306 | +5. **Commerce Event** (`testCommerceEvent`) |
| 307 | + - Creates product ("Awesome Book", SKU: 1234567890) |
| 308 | + - Logs purchase action with transaction attributes |
| 309 | + - Includes revenue, tax, shipping, coupon code |
| 310 | + |
0 commit comments