diff --git a/IntegrationTests/README.md b/IntegrationTests/README.md index 9ee050e2..f69a4380 100644 --- a/IntegrationTests/README.md +++ b/IntegrationTests/README.md @@ -71,6 +71,56 @@ Records all mParticle SDK API requests using WireMock for later use in integrati - `wiremock-recordings/mappings/*.json` - API request/response mappings - `wiremock-recordings/__files/*` - Response body files +### `extract_request_body.py` - Extract Request Body from WireMock Mapping + +Extracts JSON request body from a WireMock mapping file for easier editing and maintenance. + +```bash +# Extract request body without field replacements +python3 extract_request_body.py wiremock-recordings/mappings/mapping-v1-identify.json identify_test + +# Extract with field replacements (replaces dynamic fields with ${json-unit.ignore}) +python3 extract_request_body.py wiremock-recordings/mappings/mapping-v1-identify.json identify_test --replace +``` + +**What it does:** +- Extracts the JSON request body from WireMock mapping file +- Optionally replaces known dynamic fields (IDs, timestamps, device info) with `${json-unit.ignore}` +- Saves extracted body to `wiremock-recordings/requests/{test_name}.json` +- Makes it easier to edit and maintain test request bodies + +**Dynamic fields replaced with `--replace`:** +`a`, `bid`, `bsv`, `ct`, `das`, `dfs`, `dlc`, `dn`, `dosv`, `est`, `ict`, `id`, `lud`, `sct`, `sid`, `vid` + +**Output file format:** +```json +{ + "test_name": "identify_test", + "source_mapping": "wiremock-recordings/mappings/mapping-v1-identify.json", + "request_method": "POST", + "request_url": "/v1/identify", + "request_body": { ... } +} +``` + +### `update_mapping_from_extracted.py` - Update WireMock Mapping from Extracted Body + +Updates a WireMock mapping file with a modified request body from an extracted JSON file. + +```bash +python3 update_mapping_from_extracted.py wiremock-recordings/requests/identify_test.json +``` + +**What it does:** +- Reads the extracted request body from JSON file +- Updates the source WireMock mapping file with the modified request body +- Preserves all WireMock configuration (response, headers, etc.) +- Creates backup of original mapping file + +**Use case:** After extracting and editing a request body, use this script to apply changes back to the mapping. + +**Note:** This script is automatically called by the test runner when executing integration tests with modified request bodies. + ## Troubleshooting ### Port Already in Use / No Recordings Created @@ -101,10 +151,72 @@ If another application is using the ports, terminate it before running the scrip ## Development Workflow -1. Make changes to SDK source code -2. Run `./run_wiremock_recorder.sh` -3. Script automatically uses your latest changes, runs the app, and records API traffic -4. Review recorded mappings in `wiremock-recordings/` -5. Commit mappings to document expected API behavior +### Initial Recording of API Requests + +1. **Write test code in the integration app:** + - Make changes to SDK source code (if needed) + - Edit `IntegrationTests/Sources/main.swift` to test your new or existing SDK functionality + - Add code to call the specific SDK methods you want to record + - **Best practice:** Temporary comment out calls to unrelated your new code to record only relevant API requests + +2. **Run the WireMock recorder:** + ```bash + ./run_wiremock_recorder.sh + ``` + The script automatically uses your latest changes, runs the app, and records all API traffic + +3. **Review and filter recorded mappings:** + - All recordings are saved to `wiremock-recordings/mappings/` + - The script records **all** API requests made during the test run + - **Keep only the mappings related to your new test code** + - Delete any unrelated or duplicate recordings + + **Tip:** To get cleaner recordings, modify `main.swift` to call only the specific SDK method you're testing, avoiding unrelated API calls + +4. **Verify the recordings:** + - Check that the recorded mappings match your expected API behavior + - Review request URLs, methods, and bodies + - Verify response data in `wiremock-recordings/__files/` + +### Editing and Maintaining Test Request Bodies + +After recording, you should update request bodies to make them more maintainable (e.g., replace dynamic values): + +1. **Extract request body from mapping:** + ```bash + # Extract with automatic field replacement (recommended) + python3 extract_request_body.py \ + wiremock-recordings/mappings/mapping-v1-identify.json \ + identify_test \ + --replace + ``` + + This creates `wiremock-recordings/requests/identify_test.json` with dynamic fields replaced by `${json-unit.ignore}` + +2. **Edit the extracted request body** (optional): + - Open `wiremock-recordings/requests/identify_test.json` + - Modify the `request_body` section as needed + - Add or remove fields, change expected values, etc. + +3. **Update the mapping file with changes:** + ```bash + python3 update_mapping_from_extracted.py \ + wiremock-recordings/requests/identify_test.json + ``` + + This updates the original mapping file with your changes + +4. **Commit both files:** + ```bash + git add wiremock-recordings/mappings/mapping-v1-identify.json + git add wiremock-recordings/requests/identify_test.json + git commit -m "Update identify request mapping" + ``` + +### Running Integration Tests + +When running integration tests, the test framework will: +1. Automatically look for extracted request bodies in `wiremock-recordings/requests/` +2. Apply any changes from extracted bodies to the mappings before starting WireMock +3. Run tests against the updated mappings -**Note:** No need to rebuild the SDK separately - the project links directly to source files and automatically picks up your changes! diff --git a/IntegrationTests/Sources/main.swift b/IntegrationTests/Sources/main.swift index e6d62f3c..6b6cb5b9 100644 --- a/IntegrationTests/Sources/main.swift +++ b/IntegrationTests/Sources/main.swift @@ -1,10 +1,3 @@ -// -// main.swift -// IntegrationTests -// -// Created by Denis Chilik on 11/4/25. -// - import Foundation import mParticle_Apple_SDK diff --git a/IntegrationTests/extract_request_body.py b/IntegrationTests/extract_request_body.py new file mode 100755 index 00000000..6a6e5979 --- /dev/null +++ b/IntegrationTests/extract_request_body.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +""" +Script for extracting JSON request bodies from WireMock mappings. + +Usage: + python3 extract_request_body.py [--replace] + +Example: + python3 extract_request_body.py wiremock-recordings/mappings/mapping-v1-identify.json identify_test + python3 extract_request_body.py wiremock-recordings/mappings/mapping-v1-identify.json identify_test --replace +""" + +import json +import sys +import os +import argparse +from pathlib import Path +from typing import Dict, Any, List, Union + + +# Default list of fields to replace with ${json-unit.ignore} +# Based on existing WireMock mappings that contain dynamic/timestamp values +DEFAULT_REPLACE_FIELDS = [ + 'a', # App ID + 'bid', # Bundle ID / Build ID + 'bsv', # Build System Version + 'ct', # Creation Time / Current Time + 'das', # Device Application Stamp + 'dfs', # Device Fingerprint String + 'dlc', # Device Locale + 'dn', # Device Name + 'dosv', # Device OS Version + 'est', # Event Start Time + 'ict', # Init Config Time + 'id', # ID (various message/event IDs) + 'lud', # Last Update Date + 'sct', # Session Creation Time + 'sid', # Session ID + 'vid', # Vendor ID +] + + +def replace_field_value(data: Union[Dict, List, Any], field_name: str, replacement_value: str) -> Union[Dict, List, Any]: + """ + Recursively replaces the value of a specified field in a JSON structure. + + Args: + data: JSON data (dict, list, or primitive value) + field_name: Name of the field to replace + replacement_value: New value to set for the field + + Returns: + Modified data structure with replaced field values + + Example: + data = {"id": "123", "name": "test", "nested": {"id": "456"}} + result = replace_field_value(data, "id", "${json-unit.ignore}") + # result = {"id": "${json-unit.ignore}", "name": "test", "nested": {"id": "${json-unit.ignore}"}} + """ + if isinstance(data, dict): + # For dictionaries, check each key + result = {} + for key, value in data.items(): + if key == field_name: + # Replace the value for this field + result[key] = replacement_value + else: + # Recursively process the value + result[key] = replace_field_value(value, field_name, replacement_value) + return result + elif isinstance(data, list): + # For lists, recursively process each item + return [replace_field_value(item, field_name, replacement_value) for item in data] + else: + # For primitive values, return as is + return data + + +def replace_fields_from_list(data: Union[Dict, List, Any], field_names: List[str], replacement_value: str = "${json-unit.ignore}") -> Union[Dict, List, Any]: + """ + Replaces values of multiple fields in a JSON structure with a specified value. + + Args: + data: JSON data (dict, list, or primitive value) + field_names: List of field names to replace + replacement_value: Value to use for replacement (default: "${json-unit.ignore}") + + Returns: + Modified data structure with all specified fields replaced + + Example: + data = {"id": "123", "ct": "1234567890", "name": "test", "nested": {"id": "456", "ct": "0987654321"}} + result = replace_fields_from_list(data, ["id", "ct"]) + # result = {"id": "${json-unit.ignore}", "ct": "${json-unit.ignore}", "name": "test", + # "nested": {"id": "${json-unit.ignore}", "ct": "${json-unit.ignore}"}} + + # Or with custom replacement value: + result = replace_fields_from_list(data, ["id", "ct"], "IGNORED") + # result = {"id": "IGNORED", "ct": "IGNORED", "name": "test", "nested": {"id": "IGNORED", "ct": "IGNORED"}} + """ + result = data + + # Apply replacement for each field in the list + for field_name in field_names: + result = replace_field_value(result, field_name, replacement_value) + + return result + +def extract_request_body(mapping_file: str, test_name: str, replace_fields: bool = False) -> None: + """ + Extracts JSON body from WireMock mapping and saves it to a separate file. + + Args: + mapping_file: Path to mapping file + test_name: Test name + replace_fields: If True, replaces known dynamic fields with ${json-unit.ignore} + """ + # Check if mapping file exists + mapping_path = Path(mapping_file) + if not mapping_path.exists(): + print(f"❌ Error: mapping file not found: {mapping_file}") + sys.exit(1) + + # Read mapping file + try: + with open(mapping_path, 'r', encoding='utf-8') as f: + mapping_data = json.load(f) + except json.JSONDecodeError as e: + print(f"❌ Error: failed to parse JSON from mapping file: {e}") + sys.exit(1) + except Exception as e: + print(f"❌ Error reading mapping file: {e}") + sys.exit(1) + + # Extract request information + try: + request_data = mapping_data.get('request', {}) + method = request_data.get('method', 'UNKNOWN') + url = request_data.get('url', 'UNKNOWN') + + body_patterns = request_data.get('bodyPatterns', []) + + # Check for body presence + if not body_patterns: + # This might be a GET request or another method without body + print(f"⚠️ Warning: bodyPatterns not found in mapping") + print(f" Request method: {method}") + print(f" URL: {url}") + print(" (GET requests usually don't have a body)") + sys.exit(1) + + # Get escaped JSON string + equal_to_json = body_patterns[0].get('equalToJson') + if equal_to_json is None: + print("❌ Error: equalToJson not found in bodyPatterns") + sys.exit(1) + + # Parse escaped JSON string to get the actual JSON object + # (unescape) + request_body = json.loads(equal_to_json) + + # Apply field replacements if requested + if replace_fields: + print(f"🔄 Replacing {len(DEFAULT_REPLACE_FIELDS)} known dynamic fields with ${{json-unit.ignore}}") + request_body = replace_fields_from_list(request_body, DEFAULT_REPLACE_FIELDS) + + except json.JSONDecodeError as e: + print(f"❌ Error: failed to parse JSON from equalToJson: {e}") + sys.exit(1) + except Exception as e: + print(f"❌ Error extracting body from mapping: {e}") + sys.exit(1) + + # Form output structure + output_data = { + "test_name": test_name, + "source_mapping": str(mapping_path), + "request_method": method, + "request_url": url, + "request_body": request_body + } + + # Create directory for saving extracted bodies + output_dir = Path("wiremock-recordings/requests") + output_dir.mkdir(parents=True, exist_ok=True) + + # Form output filename + output_file = output_dir / f"{test_name}.json" + + # Save to file with pretty-print for readability + try: + with open(output_file, 'w', encoding='utf-8') as f: + json.dump(output_data, f, indent=2, ensure_ascii=False) + + print(f"✅ JSON body successfully extracted and saved to: {output_file}") + print(f"📝 Test name: {test_name}") + print(f"🔗 Source mapping: {mapping_path}") + + except Exception as e: + print(f"❌ Error saving file: {e}") + sys.exit(1) + + +def main(): + # Set up command line argument parser + parser = argparse.ArgumentParser( + description='Extract JSON request bodies from WireMock mappings', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument( + 'mapping_file', + help='Path to WireMock mapping file' + ) + + parser.add_argument( + 'test_name', + help='Test name for the output file' + ) + + parser.add_argument( + '--replace', + action='store_true', + help='Replace known dynamic fields with ${json-unit.ignore}' + ) + + args = parser.parse_args() + + extract_request_body(args.mapping_file, args.test_name, replace_fields=args.replace) + + +if __name__ == "__main__": + main() + diff --git a/IntegrationTests/update_mapping_from_extracted.py b/IntegrationTests/update_mapping_from_extracted.py new file mode 100644 index 00000000..049f8efd --- /dev/null +++ b/IntegrationTests/update_mapping_from_extracted.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +""" +Script to update WireMock mapping from extracted request body file. + +Usage: + python3 update_mapping_from_extracted.py + +Example: + python3 update_mapping_from_extracted.py wiremock-recordings/requests/identify_test.json +""" + +import json +import sys +from pathlib import Path +from typing import Dict, Any + + +def update_mapping(extracted_file: str) -> None: + """ + Updates WireMock mapping from extracted request body file. + + Args: + extracted_file: Path to extracted file + """ + # Check if file exists + extracted_path = Path(extracted_file) + if not extracted_path.exists(): + print(f"❌ Error: extracted file not found: {extracted_file}") + sys.exit(1) + + # Read extracted file + try: + with open(extracted_path, 'r', encoding='utf-8') as f: + extracted_data = json.load(f) + except json.JSONDecodeError as e: + print(f"❌ Error: failed to parse JSON from extracted file: {e}") + sys.exit(1) + except Exception as e: + print(f"❌ Error reading extracted file: {e}") + sys.exit(1) + + # Extract data + try: + test_name = extracted_data.get('test_name') + source_mapping = extracted_data.get('source_mapping') + request_body = extracted_data.get('request_body') + + if not source_mapping: + print("❌ Error: source_mapping not found in extracted file") + sys.exit(1) + + if request_body is None: + print("❌ Error: request_body not found in extracted file") + sys.exit(1) + + except Exception as e: + print(f"❌ Error extracting data from extracted file: {e}") + sys.exit(1) + + # Check if source mapping exists + mapping_path = Path(source_mapping) + if not mapping_path.exists(): + print(f"❌ Error: source mapping not found: {source_mapping}") + sys.exit(1) + + # Read source mapping + try: + with open(mapping_path, 'r', encoding='utf-8') as f: + mapping_data = json.load(f) + except json.JSONDecodeError as e: + print(f"❌ Error: failed to parse JSON from mapping: {e}") + sys.exit(1) + except Exception as e: + print(f"❌ Error reading mapping: {e}") + sys.exit(1) + + # Update request body in mapping + try: + # Convert request_body back to escaped JSON string + escaped_json = json.dumps(request_body, ensure_ascii=False, separators=(',', ':')) + + # Update bodyPatterns + if 'request' not in mapping_data: + mapping_data['request'] = {} + + if 'bodyPatterns' not in mapping_data['request']: + mapping_data['request']['bodyPatterns'] = [{}] + + mapping_data['request']['bodyPatterns'][0]['equalToJson'] = escaped_json + + except Exception as e: + print(f"❌ Error updating mapping: {e}") + sys.exit(1) + + # Save updated mapping + try: + with open(mapping_path, 'w', encoding='utf-8') as f: + json.dump(mapping_data, f, indent=2, ensure_ascii=False) + + print(f"✅ Mapping successfully updated: {mapping_path}") + print(f"📝 Test: {test_name}") + + except Exception as e: + print(f"❌ Error saving mapping: {e}") + sys.exit(1) + + +def main(): + """Main function.""" + # Check command line arguments + if len(sys.argv) != 2: + print("❌ Error: invalid number of arguments") + print() + print("Usage:") + print(f" {sys.argv[0]} ") + print() + print("Example:") + print(f" {sys.argv[0]} wiremock-recordings/requests/identify_test.json") + sys.exit(1) + + extracted_file = sys.argv[1] + + # Warning + print("⚠️ WARNING: This script will overwrite the existing mapping!") + print() + + update_mapping(extracted_file) + + +if __name__ == "__main__": + main() + diff --git a/IntegrationTests/wiremock-recordings/__files/body-v1-identify-response.json b/IntegrationTests/wiremock-recordings/__files/body-v1-identify-response.json new file mode 100644 index 00000000..bcec3f48 --- /dev/null +++ b/IntegrationTests/wiremock-recordings/__files/body-v1-identify-response.json @@ -0,0 +1 @@ +{"context":null,"matched_identities":{"customerid":"123456","email":"foo@example.com"},"is_ephemeral":false,"mpid":"6504934091054997508","is_logged_in":true} \ No newline at end of file diff --git a/IntegrationTests/wiremock-recordings/__files/body-v2-us1-log-event-response.json b/IntegrationTests/wiremock-recordings/__files/body-v2-us1-log-event-response.json new file mode 100644 index 00000000..fbe57c46 --- /dev/null +++ b/IntegrationTests/wiremock-recordings/__files/body-v2-us1-log-event-response.json @@ -0,0 +1 @@ +{"dt":"rh","id":"14ebce4f-2268-470f-a923-15415eec177b","ct":1762457826793,"msgs":[],"ci":{"mpid":6504934091054997508,"ck":{"uid":{"c":"g=24e78bf4-257d-440c-9c75-3b793cd41971","e":"2035-11-04T19:37:06.7933933Z"}},"das":"24e78bf4-257d-440c-9c75-3b793cd41971"}} \ No newline at end of file diff --git a/IntegrationTests/wiremock-recordings/__files/body-v4-us1-get-config-response.json b/IntegrationTests/wiremock-recordings/__files/body-v4-us1-get-config-response.json new file mode 100644 index 00000000..d0816632 --- /dev/null +++ b/IntegrationTests/wiremock-recordings/__files/body-v4-us1-get-config-response.json @@ -0,0 +1 @@ +{"dt":"ac","id":"03fcd379-b420-43a3-9004-c7bb821e0495","ct":1762457821105,"dbg":false,"cue":"appdefined","pmk":["mp_message","com.urbanairship.push.ALERT","alert","a","message","lp_message","gcm.notification.body"],"cnp":"appdefined","soc":0,"oo":false,"lsv":"6.13.0","rdlat":true,"inhd":false,"iasr":false,"wst":"D38F29DF","amw":90,"crml":1024000,"dur":false,"flags":{"AudienceAPI":"False"},"atos":0,"pio":30} \ No newline at end of file diff --git a/IntegrationTests/wiremock-recordings/mappings/mapping-v1-identify.json b/IntegrationTests/wiremock-recordings/mappings/mapping-v1-identify.json new file mode 100644 index 00000000..59b20aca --- /dev/null +++ b/IntegrationTests/wiremock-recordings/mappings/mapping-v1-identify.json @@ -0,0 +1,35 @@ +{ + "id" : "412faa99-da9a-3a2a-84f0-c05e3057d796", + "request" : { + "url" : "/v1/identify", + "method" : "POST", + "bodyPatterns" : [ { + "equalToJson" : "{\"client_sdk\":{\"platform\":\"ios\",\"sdk_version\":\"8.40.0\",\"sdk_vendor\":\"mparticle\"},\"environment\":\"development\",\"request_timestamp_ms\":1762457821120,\"request_id\":\"1F7C5D84-CDF2-4E68-9E4E-3D320ED84AF0\",\"known_identities\":{\"email\":\"foo@example.com\",\"customerid\":\"123456\",\"ios_idfv\":\"EE48E7EC-1EB3-468C-BAAC-2CC7DBFBDB34\",\"device_application_stamp\":\"24E78BF4-257D-440C-9C75-3B793CD41971\"}}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : true + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "body-v1-identify-response.json", + "headers" : { + "X-Cache" : "MISS", + "X-MP-Trace-Id" : "690cf8ddafe953c59086638eb093dfd5", + "Server" : "Kestrel", + "X-Origin-Name" : "4PrgpUXX9K0sNAH1JImfyI--F_us1_origin", + "X-Fastly-Trace-Id" : "932181549", + "X-MP-Max-Age" : "86400", + "Date" : "Thu, 06 Nov 2025 19:37:01 GMT", + "X-Timer" : "S1762457821.273823,VS0,VE53", + "Via" : "1.1 varnish", + "Accept-Ranges" : "bytes", + "Strict-Transport-Security" : "max-age=900", + "Access-Control-Expose-Headers" : "X-MP-Max-Age", + "X-Served-By" : "cache-ewr-kewr1740068-EWR", + "Vary" : "Accept-Encoding", + "X-Cache-Hits" : "0", + "Content-Type" : "application/json; charset=utf-8" + } + }, + "uuid" : "412faa99-da9a-3a2a-84f0-c05e3057d796" +} diff --git a/IntegrationTests/wiremock-recordings/mappings/mapping-v2-us1-e5145d11865db44eb24cd5a9f194d654-events-log-event.json b/IntegrationTests/wiremock-recordings/mappings/mapping-v2-us1-e5145d11865db44eb24cd5a9f194d654-events-log-event.json new file mode 100644 index 00000000..0edb3db4 --- /dev/null +++ b/IntegrationTests/wiremock-recordings/mappings/mapping-v2-us1-e5145d11865db44eb24cd5a9f194d654-events-log-event.json @@ -0,0 +1,31 @@ +{ + "id" : "04cb059c-f069-30d2-bc49-99cbb469a6c3", + "request" : { + "url" : "/v2/us1-e5145d11865db44eb24cd5a9f194d654/events", + "method" : "POST", + "bodyPatterns" : [ { + "equalToJson" : "{\"id\":\"${json-unit.ignore}\",\"ltv\":0,\"msgs\":[{\"vc\":\"off_thread\",\"ct\":\"${json-unit.ignore}\",\"psl\":0,\"id\":\"${json-unit.ignore}\",\"dt\":\"ss\",\"mt\":false},{\"sct\":\"${json-unit.ignore}\",\"id\":\"${json-unit.ignore}\",\"ct\":\"${json-unit.ignore}\",\"dt\":\"fr\",\"sid\":\"${json-unit.ignore}\",\"mt\":false,\"vc\":\"off_thread\"},{\"sct\":\"${json-unit.ignore}\",\"dt\":\"uic\",\"ct\":\"${json-unit.ignore}\",\"ni\":{\"dfs\":\"${json-unit.ignore}\",\"i\":\"123456\",\"n\":1,\"f\":true},\"id\":\"${json-unit.ignore}\",\"sid\":\"${json-unit.ignore}\",\"mt\":false,\"vc\":\"off_thread\"},{\"sct\":\"${json-unit.ignore}\",\"dt\":\"uic\",\"ct\":\"${json-unit.ignore}\",\"ni\":{\"dfs\":\"${json-unit.ignore}\",\"i\":\"foo@example.com\",\"n\":7,\"f\":true},\"id\":\"${json-unit.ignore}\",\"sid\":\"${json-unit.ignore}\",\"mt\":false,\"vc\":\"off_thread\"},{\"id\":\"${json-unit.ignore}\",\"vc\":\"off_thread\",\"sf\":true,\"dt\":\"ast\",\"ct\":\"${json-unit.ignore}\",\"sct\":\"${json-unit.ignore}\",\"sc\":true,\"sid\":\"${json-unit.ignore}\",\"t\":\"app_init\",\"ifr\":true,\"nsi\":0,\"mt\":false},{\"et\":\"Other\",\"id\":\"${json-unit.ignore}\",\"vc\":\"off_thread\",\"dt\":\"e\",\"el\":0,\"ct\":\"${json-unit.ignore}\",\"attrs\":{\"SimpleKey\":\"SimpleValue\"},\"sct\":\"${json-unit.ignore}\",\"sid\":\"${json-unit.ignore}\",\"en\":0,\"n\":\"Simple Event Name\",\"est\":\"${json-unit.ignore}\",\"mt\":false}],\"dt\":\"h\",\"a\":\"${json-unit.ignore}\",\"ai\":{\"av\":\"1.0\",\"sideloaded_kits_count\":0,\"bid\":\"00000000-0000-0000-0000-000000000000\",\"pir\":false,\"fi\":false,\"lud\":\"${json-unit.ignore}\",\"arc\":\"arm64\",\"tsv\":\"90000\",\"apn\":\"com.mparticle.IntegrationTests\",\"env\":1,\"abn\":\"1\",\"bsv\":\"${json-unit.ignore}\",\"ict\":\"${json-unit.ignore}\"},\"ct\":\"${json-unit.ignore}\",\"das\":\"${json-unit.ignore}\",\"mpid\":6504934091054997508,\"ui\":[{\"i\":\"123456\",\"n\":1},{\"i\":\"foo@example.com\",\"n\":7}],\"uitl\":60,\"oo\":false,\"sdk\":\"8.40.0\",\"di\":{\"p\":\"arm64\",\"bid\":\"${json-unit.ignore}\",\"tz\":\"-5\",\"dn\":\"${json-unit.ignore}\",\"dll\":\"en\",\"dlc\":\"${json-unit.ignore}\",\"dsh\":\"1440\",\"dma\":\"Apple\",\"vid\":\"${json-unit.ignore}\",\"idst\":false,\"tzn\":\"America\\/New_York\",\"dp\":\"iOS\",\"dsw\":\"960\",\"dosv\":\"${json-unit.ignore}\",\"it\":false,\"dmdl\":\"arm64\",\"dr\":\"None\",\"jb\":{\"cydia\":false},\"lat\":false,\"arc\":\"arm64e\",\"b\":\"arm64\"},\"stl\":60}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : true + } ] + }, + "response" : { + "status" : 202, + "bodyFileName" : "body-v2-us1-log-event-response.json", + "headers" : { + "X-Cache" : "MISS, MISS", + "X-MP-Trace-Id" : "690cf8e296a7a5c9974b078c5bdc6b95", + "Server" : "Kestrel", + "X-Origin-Name" : "fastlyshield--shield_ssl_cache_iad_kiad7000042_IAD", + "Date" : "Thu, 06 Nov 2025 19:37:06 GMT", + "X-Timer" : "S1762457827.775908,VS0,VE22", + "Via" : "1.1 varnish, 1.1 varnish", + "Accept-Ranges" : "bytes", + "X-Served-By" : "cache-iad-kiad7000042-IAD, cache-ewr-kewr1740038-EWR", + "Vary" : "Accept-Encoding", + "X-Cache-Hits" : "0, 0", + "Content-Type" : "application/json" + } + }, + "uuid" : "04cb059c-f069-30d2-bc49-99cbb469a6c3" +} diff --git a/IntegrationTests/wiremock-recordings/mappings/mapping-v4-us1-e5145d11865db44eb24cd5a9f194d654-config-get-config.json b/IntegrationTests/wiremock-recordings/mappings/mapping-v4-us1-e5145d11865db44eb24cd5a9f194d654-config-get-config.json new file mode 100644 index 00000000..f6745ef4 --- /dev/null +++ b/IntegrationTests/wiremock-recordings/mappings/mapping-v4-us1-e5145d11865db44eb24cd5a9f194d654-config-get-config.json @@ -0,0 +1,27 @@ +{ + "id" : "a7d323f1-a729-39c6-86cd-4035c71f7d05", + "request" : { + "url" : "/v4/us1-e5145d11865db44eb24cd5a9f194d654/config?av=1.0&sv=8.40.0", + "method" : "GET" + }, + "response" : { + "status" : 200, + "bodyFileName" : "body-v4-us1-get-config-response.json", + "headers" : { + "X-Cache" : "MISS, MISS", + "Server" : "Kestrel", + "X-Origin-Name" : "fastlyshield--shield_ssl_cache_iad_kcgs7200041_IAD", + "Date" : "Thu, 06 Nov 2025 19:37:01 GMT", + "X-Timer" : "S1762457821.099061,VS0,VE12", + "Via" : "1.1 varnish, 1.1 varnish", + "Accept-Ranges" : "bytes", + "ETag" : "030e3afc0471a01f606c7bbe1d8427ad", + "X-Served-By" : "cache-iad-kcgs7200041-IAD, cache-ewr-kewr1740036-EWR", + "Vary" : "Accept-Encoding", + "X-Cache-Hits" : "0, 0", + "Age" : "0", + "Content-Type" : "application/json" + } + }, + "uuid" : "a7d323f1-a729-39c6-86cd-4035c71f7d05" +} diff --git a/IntegrationTests/wiremock-recordings/requests/identify.json b/IntegrationTests/wiremock-recordings/requests/identify.json new file mode 100644 index 00000000..6de21497 --- /dev/null +++ b/IntegrationTests/wiremock-recordings/requests/identify.json @@ -0,0 +1,22 @@ +{ + "test_name": "identify", + "source_mapping": "wiremock-recordings/mappings/mapping-v1-identify.json", + "request_method": "POST", + "request_url": "/v1/identify", + "request_body": { + "client_sdk": { + "platform": "ios", + "sdk_version": "8.40.0", + "sdk_vendor": "mparticle" + }, + "environment": "development", + "request_timestamp_ms": 1762457821120, + "request_id": "1F7C5D84-CDF2-4E68-9E4E-3D320ED84AF0", + "known_identities": { + "email": "foo@example.com", + "customerid": "123456", + "ios_idfv": "EE48E7EC-1EB3-468C-BAAC-2CC7DBFBDB34", + "device_application_stamp": "24E78BF4-257D-440C-9C75-3B793CD41971" + } + } +} \ No newline at end of file diff --git a/IntegrationTests/wiremock-recordings/requests/log_event.json b/IntegrationTests/wiremock-recordings/requests/log_event.json new file mode 100644 index 00000000..dff825f5 --- /dev/null +++ b/IntegrationTests/wiremock-recordings/requests/log_event.json @@ -0,0 +1,149 @@ +{ + "test_name": "log_event", + "source_mapping": "wiremock-recordings/mappings/mapping-v2-us1-e5145d11865db44eb24cd5a9f194d654-events-log-event.json", + "request_method": "POST", + "request_url": "/v2/us1-e5145d11865db44eb24cd5a9f194d654/events", + "request_body": { + "id": "${json-unit.ignore}", + "ltv": 0, + "msgs": [ + { + "vc": "off_thread", + "ct": "${json-unit.ignore}", + "psl": 0, + "id": "${json-unit.ignore}", + "dt": "ss", + "mt": false + }, + { + "sct": "${json-unit.ignore}", + "id": "${json-unit.ignore}", + "ct": "${json-unit.ignore}", + "dt": "fr", + "sid": "${json-unit.ignore}", + "mt": false, + "vc": "off_thread" + }, + { + "sct": "${json-unit.ignore}", + "dt": "uic", + "ct": "${json-unit.ignore}", + "ni": { + "dfs": "${json-unit.ignore}", + "i": "123456", + "n": 1, + "f": true + }, + "id": "${json-unit.ignore}", + "sid": "${json-unit.ignore}", + "mt": false, + "vc": "off_thread" + }, + { + "sct": "${json-unit.ignore}", + "dt": "uic", + "ct": "${json-unit.ignore}", + "ni": { + "dfs": "${json-unit.ignore}", + "i": "foo@example.com", + "n": 7, + "f": true + }, + "id": "${json-unit.ignore}", + "sid": "${json-unit.ignore}", + "mt": false, + "vc": "off_thread" + }, + { + "id": "${json-unit.ignore}", + "vc": "off_thread", + "sf": true, + "dt": "ast", + "ct": "${json-unit.ignore}", + "sct": "${json-unit.ignore}", + "sc": true, + "sid": "${json-unit.ignore}", + "t": "app_init", + "ifr": true, + "nsi": 0, + "mt": false + }, + { + "et": "Other", + "id": "${json-unit.ignore}", + "vc": "off_thread", + "dt": "e", + "el": 0, + "ct": "${json-unit.ignore}", + "attrs": { + "SimpleKey": "SimpleValue" + }, + "sct": "${json-unit.ignore}", + "sid": "${json-unit.ignore}", + "en": 0, + "n": "Simple Event Name", + "est": "${json-unit.ignore}", + "mt": false + } + ], + "dt": "h", + "a": "${json-unit.ignore}", + "ai": { + "av": "1.0", + "sideloaded_kits_count": 0, + "bid": "${json-unit.ignore}", + "pir": false, + "fi": false, + "lud": "${json-unit.ignore}", + "arc": "arm64", + "tsv": "90000", + "apn": "com.mparticle.IntegrationTests", + "env": 1, + "abn": "1", + "bsv": "${json-unit.ignore}", + "ict": "${json-unit.ignore}" + }, + "ct": "${json-unit.ignore}", + "das": "${json-unit.ignore}", + "mpid": 6504934091054997508, + "ui": [ + { + "i": "123456", + "n": 1 + }, + { + "i": "foo@example.com", + "n": 7 + } + ], + "uitl": 60, + "oo": false, + "sdk": "8.40.0", + "di": { + "p": "arm64", + "bid": "${json-unit.ignore}", + "tz": "-5", + "dn": "${json-unit.ignore}", + "dll": "en", + "dlc": "${json-unit.ignore}", + "dsh": "1440", + "dma": "Apple", + "vid": "${json-unit.ignore}", + "idst": false, + "tzn": "America/New_York", + "dp": "iOS", + "dsw": "960", + "dosv": "${json-unit.ignore}", + "it": false, + "dmdl": "arm64", + "dr": "None", + "jb": { + "cydia": false + }, + "lat": false, + "arc": "arm64e", + "b": "arm64" + }, + "stl": 60 + } +} \ No newline at end of file