From 85152c2fe7927c683e4ad8c87242960adf1ad503 Mon Sep 17 00:00:00 2001 From: "Daniel S. Billing" Date: Sat, 16 Sep 2023 13:47:11 +0200 Subject: [PATCH 1/7] Only add optionals to payload if filled --- oppe/oppe.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/oppe/oppe.py b/oppe/oppe.py index 5be0d42..7eaaca4 100644 --- a/oppe/oppe.py +++ b/oppe/oppe.py @@ -55,19 +55,24 @@ def event(self, channel_id, title, description=None, emoji=None, data=None): if not validate_uuid(uuid_input=channel_id): raise UuidValidationError(msg='Channel ID must be a valid UUID') - # Make sure data is an object - if not data: - data = {} - # Create payload to send to Oppe payload = { 'channel_id': channel_id, 'title': title, - 'description': description, - 'emoji': emoji, - 'data': json.dumps(data), } + # If description is not None, add it to the payload + if description: + payload['description'] = description + + # If emoji is not None, add it to the payload + if emoji: + payload['emoji'] = emoji + + # If data is not None, add it to the payload + if data: + payload['data'] = json.dumps(data) + # Send request to Oppe response = requests.post(Config.EVENT_URL, data=json.dumps(payload), headers=request_header(api_token=self.api_token)) if response.status_code != 201: From 460c76c478fba80604bbce26702e6d993026073c Mon Sep 17 00:00:00 2001 From: "Daniel S. Billing" Date: Sat, 16 Sep 2023 14:28:05 +0200 Subject: [PATCH 2/7] Added test for invalid api token --- requirements-dev.txt | 1 + tests/test_oppe.py | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 5bcc559..98e7ad9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,3 +10,4 @@ Faker~=19.6.1 pytest~=7.4.0 pytest-cov~=4.1.0 pytest-sugar~=0.9.7 +pytest-mock~=3.11.1 diff --git a/tests/test_oppe.py b/tests/test_oppe.py index e67756b..4dd4085 100644 --- a/tests/test_oppe.py +++ b/tests/test_oppe.py @@ -1,9 +1,11 @@ import os +from unittest.mock import Mock +import pytest from dotenv import load_dotenv from faker import Faker -from oppe.exceptions import UuidValidationError +from oppe.exceptions import EventRequestError, UuidValidationError from oppe.oppe import Oppe fake = Faker() @@ -82,3 +84,16 @@ def test_publish_event_with_wrong_channel_id(): oppe.event(channel_id=fake.sha256(), title=fake.domain_word()) except UuidValidationError as e: assert e.msg == 'Channel ID must be a valid UUID' + + +def test_invalid_api_token(mocker): + """ Test invalid api token """ + # Mock the requests.post method to return a non-201 response + mocker.patch('requests.post', return_value=Mock(status_code=401, json=lambda: {'error': 'Unauthorized'})) + + # Create an instance of the Oppe class + oppe = Oppe(api_token='invalid_token', project_id=os.getenv('TEST_PROJECT_ID')) + + # Call the event method with valid parameters + with pytest.raises(EventRequestError): + oppe.event(channel_id=os.getenv('TEST_CHANNEL_ID'), title=fake.domain_word()) From 8dd7719b4c27566832afcb00cf906a1b457f7582 Mon Sep 17 00:00:00 2001 From: "Daniel S. Billing" Date: Sat, 16 Sep 2023 14:28:26 +0200 Subject: [PATCH 3/7] Added tests for exceptions --- tests/test_exceptions.py | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/test_exceptions.py diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py new file mode 100644 index 0000000..4ecb84d --- /dev/null +++ b/tests/test_exceptions.py @@ -0,0 +1,42 @@ +from oppe.exceptions import EventRequestError, OppeError, UuidValidationError + + +def test_default_parameters(): + """ Test default parameters """ + error = OppeError() + assert error.msg == 'Unknown error occurred' + assert error.data is None + + +def test_custom_parameters(): + """ Test custom parameters """ + error = OppeError(msg='Custom error message', data={'key': 'value'}) + assert error.msg == 'Custom error message' + assert error.data == {'key': 'value'} + + +def test_empty_string_message(): + """ Test empty string message """ + error = OppeError('') + assert error.msg == '' + assert error.data is None + + +def test_with_only_msg_attribute(): + """ Test with only msg attribute """ + error = OppeError('Error message') + assert str(error) == 'Error message' + + +def test_subclass_uuid_validation_error(): + """ Test subclass UuidValidationError """ + error = UuidValidationError(msg='Uuid Validation Error') + assert error.msg == 'Uuid Validation Error' + assert error.data is None + + +def test_subclass_event_request_error(): + """ Test subclass EventRequestError """ + error = EventRequestError(msg='Event Request Error') + assert error.msg == 'Event Request Error' + assert error.data is None From 1825d10ccb3ba6fe49040f62d5817d8ea921f3c0 Mon Sep 17 00:00:00 2001 From: "Daniel S. Billing" Date: Sat, 16 Sep 2023 14:42:01 +0200 Subject: [PATCH 4/7] Refactor env keys for not interfere with other keys --- .env.example | 8 ++++---- oppe/config.py | 2 +- tests/test_oppe.py | 22 +++++++++++----------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.env.example b/.env.example index 026c60a..fc2452a 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,5 @@ -ENV=staging +OPPE_ENV=staging -TEST_API_TOKEN= -TEST_PROJECT_ID= -TEST_CHANNEL_ID= +OPPE_TEST_API_TOKEN= +OPPE_TEST_PROJECT_ID= +OPPE_TEST_CHANNEL_ID= diff --git a/oppe/config.py b/oppe/config.py index 4522190..b529001 100644 --- a/oppe/config.py +++ b/oppe/config.py @@ -21,7 +21,7 @@ class Config: if os.path.exists(dotenv_path): load_dotenv(dotenv_path) - ENV = os.getenv('ENV') + ENV = os.getenv('OPPE_ENV') if ENV == 'staging': BASE_URL = 'https://staging.oppe.app/api' diff --git a/tests/test_oppe.py b/tests/test_oppe.py index 4dd4085..018a680 100644 --- a/tests/test_oppe.py +++ b/tests/test_oppe.py @@ -18,62 +18,62 @@ def init_oppe(): """ Initialize Oppe """ - return Oppe(api_token=os.getenv('TEST_API_TOKEN'), project_id=os.getenv('TEST_PROJECT_ID')) + return Oppe(api_token=os.getenv('OPPE_TEST_API_TOKEN'), project_id=os.getenv('OPPE_TEST_PROJECT_ID')) def test_publish_event(): """ Test publish event """ oppe = init_oppe() - response = oppe.event(channel_id=os.getenv('TEST_CHANNEL_ID'), title=fake.domain_word(), description=fake.sentence()) + response = oppe.event(channel_id=os.getenv('OPPE_TEST_CHANNEL_ID'), title=fake.domain_word(), description=fake.sentence()) assert response is True def test_publish_event_with_emoji(): """ Test publish event with emoji """ oppe = init_oppe() - response = oppe.event(channel_id=os.getenv('TEST_CHANNEL_ID'), title=fake.domain_word(), description=fake.sentence(), emoji='👋') + response = oppe.event(channel_id=os.getenv('OPPE_TEST_CHANNEL_ID'), title=fake.domain_word(), description=fake.sentence(), emoji='👋') assert response is True def test_publish_event_with_data(): """ Test publish event with data """ oppe = init_oppe() - response = oppe.event(channel_id=os.getenv('TEST_CHANNEL_ID'), title=fake.domain_word(), description=fake.sentence(), data={'user_id': 1}) + response = oppe.event(channel_id=os.getenv('OPPE_TEST_CHANNEL_ID'), title=fake.domain_word(), description=fake.sentence(), data={'user_id': 1}) assert response is True def test_publish_event_with_emoji_and_data(): """ Test publish event with emoji and data """ oppe = init_oppe() - response = oppe.event(channel_id=os.getenv('TEST_CHANNEL_ID'), title=fake.domain_word(), description=fake.sentence(), emoji='👋', data={'user_id': 1}) + response = oppe.event(channel_id=os.getenv('OPPE_TEST_CHANNEL_ID'), title=fake.domain_word(), description=fake.sentence(), emoji='👋', data={'user_id': 1}) assert response is True def test_publish_event_with_emoji_and_data_and_no_description(): """ Test publish event with emoji and data and no description """ oppe = init_oppe() - response = oppe.event(channel_id=os.getenv('TEST_CHANNEL_ID'), title=fake.domain_word(), emoji='👋', data={'user_id': 1}) + response = oppe.event(channel_id=os.getenv('OPPE_TEST_CHANNEL_ID'), title=fake.domain_word(), emoji='👋', data={'user_id': 1}) assert response is True def test_publish_event_with_emoji_and_no_description(): """ Test publish event with emoji and no description """ oppe = init_oppe() - response = oppe.event(channel_id=os.getenv('TEST_CHANNEL_ID'), title=fake.domain_word(), emoji='👋') + response = oppe.event(channel_id=os.getenv('OPPE_TEST_CHANNEL_ID'), title=fake.domain_word(), emoji='👋') assert response is True def test_publish_event_with_data_and_no_description(): """ Test publish event with data and no description """ oppe = init_oppe() - response = oppe.event(channel_id=os.getenv('TEST_CHANNEL_ID'), title=fake.domain_word(), data={'user_id': 1}) + response = oppe.event(channel_id=os.getenv('OPPE_TEST_CHANNEL_ID'), title=fake.domain_word(), data={'user_id': 1}) assert response is True def test_publish_event_with_no_description(): """ Test publish event with no description """ oppe = init_oppe() - response = oppe.event(channel_id=os.getenv('TEST_CHANNEL_ID'), title=fake.domain_word()) + response = oppe.event(channel_id=os.getenv('OPPE_TEST_CHANNEL_ID'), title=fake.domain_word()) assert response is True @@ -92,8 +92,8 @@ def test_invalid_api_token(mocker): mocker.patch('requests.post', return_value=Mock(status_code=401, json=lambda: {'error': 'Unauthorized'})) # Create an instance of the Oppe class - oppe = Oppe(api_token='invalid_token', project_id=os.getenv('TEST_PROJECT_ID')) + oppe = Oppe(api_token='invalid_token', project_id=os.getenv('OPPE_TEST_PROJECT_ID')) # Call the event method with valid parameters with pytest.raises(EventRequestError): - oppe.event(channel_id=os.getenv('TEST_CHANNEL_ID'), title=fake.domain_word()) + oppe.event(channel_id=os.getenv('OPPE_TEST_CHANNEL_ID'), title=fake.domain_word()) From 3409adc2da630a2bb32501eddc732be1b953ec7e Mon Sep 17 00:00:00 2001 From: "Daniel S. Billing" Date: Sat, 16 Sep 2023 14:44:35 +0200 Subject: [PATCH 5/7] Refactor env keys --- .github/workflows/code-style.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml index 0495ae2..5fd5bd4 100644 --- a/.github/workflows/code-style.yml +++ b/.github/workflows/code-style.yml @@ -37,9 +37,9 @@ jobs: run: mv .env.example .env - name: Change environment variables in .env run: | - sed -i "s/TEST_API_TOKEN=/TEST_API_TOKEN=${{ secrets.TEST_API_TOKEN }}/g" .env - sed -i "s/TEST_PROJECT_ID=/TEST_PROJECT_ID=${{ secrets.TEST_PROJECT_ID }}/g" .env - sed -i "s/TEST_CHANNEL_ID=/TEST_CHANNEL_ID=${{ secrets.TEST_CHANNEL_ID }}/g" .env + sed -i "s/OPPE_TEST_API_TOKEN=/OPPE_TEST_API_TOKEN=${{ secrets.OPPE_TEST_API_TOKEN }}/g" .env + sed -i "s/OPPE_TEST_PROJECT_ID=/OPPE_TEST_PROJECT_ID=${{ secrets.OPPE_TEST_PROJECT_ID }}/g" .env + sed -i "s/OPPE_TEST_CHANNEL_ID=/OPPE_TEST_CHANNEL_ID=${{ secrets.OPPE_TEST_CHANNEL_ID }}/g" .env - name: Run Tests run: | pytest --disable-pytest-warnings From 6bcfe8d4120eca10fa9a04f61f4a37ef0cab9e3d Mon Sep 17 00:00:00 2001 From: "Daniel S. Billing" Date: Sat, 16 Sep 2023 15:09:32 +0200 Subject: [PATCH 6/7] Added Codecov --- .github/workflows/code-style.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml index 5fd5bd4..63591ac 100644 --- a/.github/workflows/code-style.yml +++ b/.github/workflows/code-style.yml @@ -42,4 +42,8 @@ jobs: sed -i "s/OPPE_TEST_CHANNEL_ID=/OPPE_TEST_CHANNEL_ID=${{ secrets.OPPE_TEST_CHANNEL_ID }}/g" .env - name: Run Tests run: | - pytest --disable-pytest-warnings + pytest --disable-pytest-warnings --cov --cov-report=xml --cov-report=term-missing + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From 625e68a04dac8763700b8ca48b581c5c46c6d8e0 Mon Sep 17 00:00:00 2001 From: "Daniel S. Billing" Date: Sat, 16 Sep 2023 15:13:08 +0200 Subject: [PATCH 7/7] Added badges to readme --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 3a7ad05..7534acc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ # oppe-for-python +[![codecov](https://codecov.io/gh/kilobyteno/oppe-for-python/graph/badge.svg?token=JKLDG13D1W)](https://codecov.io/gh/kilobyteno/oppe-for-python) +[![PyPI version](https://badge.fury.io/py/oppe.svg)](https://badge.fury.io/py/oppe) +[![Downloads](https://pepy.tech/badge/oppe)](https://pepy.tech/project/oppe) +[![License](https://img.shields.io/github/license/kilobyteno/oppe-for-python)](LICENSE) + An API wrapper for [Oppe](https://oppe.app) written in Python.