diff --git a/.gitignore b/.gitignore index b856542..ebecd38 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ build .vscode/ .idea/ .python-version +.venv +.DS_Store \ No newline at end of file diff --git a/e2e-test/.env b/e2e-test/.env new file mode 100644 index 0000000..35228c4 --- /dev/null +++ b/e2e-test/.env @@ -0,0 +1,4 @@ +APP_NAME='e2e-test' +DEBUG_MODE = False +SEND_EVENTS = True +LOG_FORMAT='%(asctime)s - %(name)s - %(levelname)s - %(message)s' \ No newline at end of file diff --git a/e2e-test/README.md b/e2e-test/README.md new file mode 100644 index 0000000..6488220 --- /dev/null +++ b/e2e-test/README.md @@ -0,0 +1,95 @@ + +# Analytics Python CLI + +This tool is created for the purpose of E2E Testing. + +## Dependencies + +| Module | Version | +|--|--| +| python | 3.9 | +| click | 8.1.8 | +| python-dotenv | 1.0.1 | +| python-dateutil | 2.8.2 | +| requests | 2.32.3 | +| PyJWT | 2.10.1 | +| backoff | 2.2.1 | + +## Installation + + 1. Change the working directory + ```bash + $ cd e2e-test + ``` + 2. Create a virtual environment inside the working directory + ```bash + $ python3 -m venv .venv + ``` + 3. Enable the virtual environment + ```bash + $ source .venv/bin/activate + ``` + 4. Install dependencies + ```bash + $ pip install -r requirements.txt + ``` + 5. Install the script as a module + ```bash + $ pip install --editable . + ``` + +## Usage Examples with Sample Payloads + +| Command Option | Type | Description | Required | +|--|--|--|--| +| --writeKey | String | Segment Write Key | Yes | +| --apiHost | String | Custom Host | No | +| --payload | JSON | Event Payload | Yes | + +### Example: Passing Multiple Events as JSON Array + +```bash +$ e2e-test:run --writeKey='YOUR_WRITE_KEY' --apiHost='' --payload='"[{\"anonymousId\":\"507f191e810c89729de960ea\",\"channel\":\"browser\",\"context\":{\"app\":\"ecommerce\"},\"integrations\":{\"All\":false,\"Mixpanel\":true,\"Salesforce\":true},\"messageId\":\"022ty90c-bbac-11e4-8dfc-aa07a5b093q8\",\"traits\":{\"name\":\"Clark Kent\",\"email\":\"clark@example.com\",\"plan\":\"premium\",\"logins\":5,\"address\":{\"street\":\"6th St\",\"city\":\"San Francisco\",\"state\":\"CA\",\"postalCode\":\"94103\",\"country\":\"USA\"}},\"type\":\"identify\",\"userId\":\"AiUGstSDIg\",\"version\":\"2.0\"},{\"messageId\":\"122bb9ui-bbac-11e4-8dfc-aa07z5b098ip\",\"userId\":\"AiUGstSDIg\",\"type\":\"track\",\"event\":\"Course Clicked\",\"context\":{\"page\":{\"path\":\"/academy/\",\"referrer\":\"\",\"search\":\"\",\"title\":\"Analytics Academy\",\"url\":\"https://segment.com/academy/\"}},\"integrations\":{},\"properties\":{\"title\":\"Intro to Analytics\"}}]"' +``` + +### Example: Passing Individual Events + +#### 1. Identify + + ``` bash +$ e2e-test:run --writeKey='YOUR_WRITE_KEY' --payload='"{\"anonymousId\":\"507f191e810c89729de960ea\",\"channel\":\"browser\",\"context\":{\"app\":\"ecommerce\"},\"integrations\":{\"All\":false,\"Mixpanel\":true,\"Salesforce\":true},\"messageId\":\"022bb90c-bbac-11e4-8dfc-aa07a5b093q8\",\"traits\":{\"name\":\"Clark Kent\",\"email\":\"clark@example.com\",\"plan\":\"premium\",\"logins\":5,\"address\":{\"street\":\"6th St\",\"city\":\"San Francisco\",\"state\":\"CA\",\"postalCode\":\"94103\",\"country\":\"USA\"}},\"type\":\"identify\",\"userId\":\"97980cfea0062\",\"version\":\"2.0\"}"' +``` + +#### 2. Track + +``` bash +$ e2e-test:run --writeKey='YOUR_WRITE_KEY' --payload='"{\"messageId\":\"122bb90c-bbac-11e4-8dfc-aa07z5b098ip\",\"userId\":\"AiUGstSDIg\",\"type\":\"track\",\"event\":\"Course Clicked\",\"context\":{\"page\":{\"path\":\"/academy/\",\"referrer\":\"\",\"search\":\"\",\"title\":\"Analytics Academy\",\"url\":\"https://segment.com/academy/\"}},\"integrations\":{},\"properties\":{\"title\":\"Intro to Analytics\"}}"' +``` + +#### 3. Page + +``` bash +$ e2e-test:run --writeKey='YOUR_WRITE_KEY' --payload='"{\"anonymousId\":\"507f191e810c19729de860ea\",\"channel\":\"browser\",\"integrations\":{\"All\":true,\"Mixpanel\":false,\"Salesforce\":false},\"messageId\":\"022bb90c-bbac-11e8-8dfc-aa07a5b090ol\",\"name\":\"Home\",\"properties\":{\"title\":\"Welcome | Initech\",\"url\":\"http://www.example.com\"},\"type\":\"page\",\"userId\":\"97980cfea0067\"}"' +``` + +#### 4. Screen + +``` bash +$ e2e-test:run --writeKey='YOUR_WRITE_KEY' --payload='"{\"anonymousId\":\"507f191e810c19729de860ea\",\"channel\":\"browser\",\"integrations\":{\"All\":true,\"Mixpanel\":false,\"Salesforce\":false},\"messageId\":\"022bb90c-bbac-11e8-8dfc-aa07a5b090ol\",\"name\":\"Registration\",\"properties\":{\"title\":\"Welcome | Initech\",\"url\":\"http://www.example.com\"},\"type\":\"Screen\",\"userId\":\"97980cfea0067\"}"' +``` + +#### 5. Alias + +``` bash +$ e2e-test:run --writeKey='YOUR_WRITE_KEY' --payload='"{\"anonymousId\":\"507f191e810c19729de800ea\",\"channel\":\"browser\",\"integrations\":{\"All\":true,\"Mixpanel\":false,\"Salesforce\":false},\"messageId\":\"022bb90c-bbac-11e4-8dfc-aa07u5b093lk\",\"previousId\":\"12345-239239-239239-23923\",\"type\":\"alias\",\"userId\":\"507f191e81\",\"version\":\"1.9\"}"' +``` + +#### 6. Group + +``` bash +% e2e-test:run --writeKey='YOUR_WRITE_KEY' --payload='"{\"anonymousId\":\"507f191e810c19729de800ea\",\"channel\":\"browser\",\"integrations\":{\"All\":true,\"Mixpanel\":false,\"Salesforce\":false},\"messageId\":\"022bb90c-bbac-11e4-8dfc-aa07u5b093lk\",\"previousId\":\"12345-239239-239239-23923\",\"type\":\"alias\",\"userId\":\"507f191e81\",\"version\":\"1.9\"}"' +``` + +## Configuration Options + +A few configuration options are available in the ```.env``` file. \ No newline at end of file diff --git a/e2e-test/__init__.py b/e2e-test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/e2e-test/main.py b/e2e-test/main.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/e2e-test/main.py @@ -0,0 +1 @@ + diff --git a/e2e-test/requirements.txt b/e2e-test/requirements.txt new file mode 100644 index 0000000..bbbf794 --- /dev/null +++ b/e2e-test/requirements.txt @@ -0,0 +1,6 @@ +click==8.1.8 +python-dotenv==1.0.1 +python-dateutil==2.8.2 +requests==2.32.3 +PyJWT==2.10.1 +backoff==2.2.1 \ No newline at end of file diff --git a/e2e-test/setup.py b/e2e-test/setup.py new file mode 100644 index 0000000..6752f56 --- /dev/null +++ b/e2e-test/setup.py @@ -0,0 +1,16 @@ +from setuptools import setup, find_packages + +setup( + name='e2e-test', + version='0.1.0', + packages=find_packages(), + include_package_data=True, + install_requires=[ + 'click', 'python-dotenv', 'python-dateutil', 'requests', 'PyJWT', 'backoff' + ], + entry_points={ + 'console_scripts': [ + 'e2e-test:run = src.cli:run', + ], + }, +) diff --git a/e2e-test/src/__init__.py b/e2e-test/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/e2e-test/src/cli.py b/e2e-test/src/cli.py new file mode 100644 index 0000000..b5bded3 --- /dev/null +++ b/e2e-test/src/cli.py @@ -0,0 +1,102 @@ +import click +from dotenv import load_dotenv +import os +import sys +import logging +import json +from collections.abc import Iterable + +load_dotenv() + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))) + +import segment.analytics as analytics # noqa: E402 (ignore autopep8) + + +@click.command() +@click.option('--writeKey', type=str, help='Segment write key') +@click.option('--apiHost', type=str, help='Custom host') +@click.option('--payload', type=str, help='A JSON string that specifies the event payload.') +def run(writekey, payload, apihost=None): + analytics.write_key = writekey + + if apihost is not None: + analytics.host = apihost # Set custom host + + analytics.debug = os.getenv('DEBUG_MODE') + analytics.send = os.getenv('SEND_EVENTS') + logger = log_config() + + try: + # Decode the JSON payload + decodedJson = json.loads(payload) + dataObject = json.loads(decodedJson) + + # To ensure for loop do not raise exception when individual JSON is passed + # we are converting the payload to a List having a single Dictionary as its item + + if not isinstance(dataObject, Iterable): # Check if dataObject is non-iterable + dataObject = [dataObject] + elif isinstance(dataObject, dict): # Check if dataObject is a dictionary + dataObject = [dataObject] + + # Iterate over each item in the payload JSON + for data in dataObject: + + specType = data.get('type') if data.get('type') is not None else None + messageId = data.get('messageId') if data.get('messageId') is not None else None + userId = data.get('userId') if data.get('userId') is not None else '' + eventName = data.get('event') if data.get('event') is not None else None + traits = data.get('traits') if data.get('traits') is not None else None + properties = data.get('properties') if data.get('properties') is not None else None + context = data.get('context') if data.get('context') is not None else None + integrations = data.get('integrations') if data.get('integrations') is not None else None + groupId = data.get('groupId') if data.get('groupId') is not None else None + pageOrScreenName = data.get('name') if data.get('name') is not None else None + pageOrScreenCategory = data.get('category') if data.get('category') is not None else None + timestamp = data.get('timestamp') if data.get('timestamp') is not None else None + anonymousId = data.get('anonymousId') if data.get('anonymousId') is not None else '' + previousId = data.get('previousId') if data.get('previousId') is not None else None + + if specType == 'identify': + analytics.identify(userId, traits, context, timestamp, anonymousId, integrations, messageId) + elif specType == 'track': + analytics.track(userId, eventName, properties, context, timestamp, anonymousId, integrations, messageId) + elif specType == 'page': + analytics.page(userId, pageOrScreenCategory, pageOrScreenName, properties, + context, timestamp, anonymousId, integrations, messageId) + elif specType == 'screen': + analytics.screen(userId, pageOrScreenCategory, pageOrScreenName, properties, + context, timestamp, anonymousId, integrations, messageId) + elif specType == 'alias': + analytics.alias(previousId, userId, context, timestamp, integrations, messageId) + elif specType == 'group': + analytics.group(userId, groupId, traits, context, timestamp, anonymousId, integrations, messageId) + else: + raise Exception + except Exception as e: + logger.exception(e) + finally: + analytics.flush() + + +def log_config(): + # Create a logger object + logger = logging.getLogger(os.getenv('APP_NAME')) + logger.setLevel(logging.DEBUG) + + handler = logging.StreamHandler() + handler.setLevel(logging.DEBUG) + + # Define the log message format + formatter = logging.Formatter(os.getenv('LOG_FORMAT')) + handler.setFormatter(formatter) + + # Attach the handler to the logger + logger.addHandler(handler) + + return logger + + +if __name__ == '__main__': + run()