Skip to content

Commit

Permalink
Added/fixed the Discourse backend. (#584)
Browse files Browse the repository at this point in the history
  • Loading branch information
facundobatista authored Dec 8, 2023
1 parent 7bab58a commit 722673c
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 94 deletions.
8 changes: 0 additions & 8 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,3 @@ services:
- TELEGRAM_PUBLIC_CHAT_ID=${TELEGRAM_PUBLIC_CHAT_ID}
- FACEBOOK_PAGE_ACCESS_TOKEN=${FACEBOOK_PAGE_ACCESS_TOKEN}
- FACEBOOK_PAGE_ID=${FACEBOOK_PAGE_ID}
- TWITTER_ACCESS_TOKEN=${TWITTER_ACCESS_TOKEN}
- TWITTER_ACCESS_SECRET=${TWITTER_ACCESS_SECRET}
- TWITTER_CONSUMER_KEY=${TWITTER_CONSUMER_KEY}
- TWITTER_CONSUMER_SECRET=${TWITTER_CONSUMER_SECRET}
- DISCOURSE_HOST=${DISCOURSE_HOST}
- DISCOURSE_API_KEY=${DISCOURSE_API_KEY}
- DISCOURSE_USERNAME=${DISCOURSE_USERNAME}
- DISCOURSE_CATEGORY=${DISCOURSE_CATEGORY}
27 changes: 11 additions & 16 deletions joboffers/publishers/discourse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@

from joboffers.publishers import Publisher


DISCOURSE_POST_URL = f'http://{settings.DISCOURSE_HOST}/posts'

ERROR_LOG_MESSAGE = 'Falló al querer publicar a discourse, url=%s data=%s: %s'

MIN_LENGTH_POST_TITLE = 20


Expand All @@ -26,13 +21,11 @@ def _push_to_api(self, message: str, title: str, link: str):
'Api-Username': settings.DISCOURSE_USERNAME,
}

uuid_suffix = str(uuid.uuid4())
uuid_suffix = uuid.uuid4().hex

# make the title unique by appending part of an uuid; however if the title
# is too short for discourse use the whole uuid
post_title = f'{title} - {uuid_suffix[:8]}'

# If post_title is shorther than the minimum required length, complete
# with the full uuid_suffix. Even the uuid is finite, it should be
# long enough to complete a reasonable title length.
if len(post_title) < MIN_LENGTH_POST_TITLE:
post_title = f"{title} - {uuid_suffix}"

Expand All @@ -42,15 +35,17 @@ def _push_to_api(self, message: str, title: str, link: str):
'category': settings.DISCOURSE_CATEGORY,
}

url = f'{settings.DISCOURSE_BASE_URL}/posts.json'
try:
result = requests.post(DISCOURSE_POST_URL, json=payload, headers=headers)
resp = requests.post(url, json=payload, headers=headers)
except Exception as err:
logging.error("Unknown error when publishing: %r", err)
status = None
result_info = err
else:
status = result.status_code
result_info = result.text
status = resp.status_code
if status != requests.codes.ok:
logging.error(
"Bad server response when publishing: %s (%r); title=%r message=%r",
status, resp.text, post_title, message)

if status != requests.codes.ok:
logging.error(ERROR_LOG_MESSAGE, DISCOURSE_POST_URL, payload, result_info)
return status
115 changes: 48 additions & 67 deletions joboffers/tests/test_discourse_publisher.py
Original file line number Diff line number Diff line change
@@ -1,98 +1,79 @@
import uuid
from unittest.mock import patch

from requests_mock.exceptions import NoMockAddress
from requests_mock.mocker import Mocker
from ..publishers.discourse import DiscoursePublisher

from ..publishers.discourse import (
ERROR_LOG_MESSAGE,
DISCOURSE_POST_URL,
DiscoursePublisher,
)

DUMMY_MESSAGE = 'message'
DUMMY_TITLE = 'This is a title with the right length'
DUMMY_TITLE_SHORT = 'Short'
DUMMY_LINK = 'https://example.com/'
DUMMY_CATEGORY = '1'
DUMMY_EXCEPTION_MESSAGE = 'Oops'
DUMMY_BAD_REQUEST_TEXT = 'This is bad'
DUMMY_UUID = 'aafd27ff-8baf-433b-82eb-8c7fada847da'
DUMMY_MESSAGE = "message"
DUMMY_TITLE = "This is a title with the right length"
DUMMY_LINK = "https://example.com/"
DUMMY_CATEGORY = "1"
DUMMY_UUID = uuid.UUID("ce322dce-ef12-4393-bebc-1ad56e4007fd")


class DummyRequest:
status_code = 400
text = DUMMY_BAD_REQUEST_TEXT


def mocked_uuid():
"""Mock of uuid4 method."""
return DUMMY_UUID


@patch('joboffers.publishers.discourse.uuid.uuid4')
def test_publish_message_ok(uuid_mock, requests_mock: Mocker, settings):
"""Test that a requests made to the facebook api is ok."""
requests_mock.post(DISCOURSE_POST_URL, json='', status_code=200)
uuid_mock.return_value = DUMMY_UUID
@patch("joboffers.publishers.discourse.uuid.uuid4", return_value=DUMMY_UUID)
def test_publish_message_ok(uuid_mock, requests_mock, settings):
"""Request made to the Discourse API is ok."""
settings.DISCOURSE_CATEGORY = DUMMY_CATEGORY
try:
status = DiscoursePublisher()._push_to_api(DUMMY_MESSAGE, DUMMY_TITLE, DUMMY_LINK)
except NoMockAddress:
assert (
False
), 'publish_offer raised an exception, wich means that the url is malformed.'
settings.DISCOURSE_BASE_URL = "https://localhost:9876"
requests_mock.post("https://localhost:9876/posts.json", json="", status_code=200)

post_title = f'{DUMMY_TITLE} - {DUMMY_UUID[:8]}'
status = DiscoursePublisher()._push_to_api(DUMMY_MESSAGE, DUMMY_TITLE, DUMMY_LINK)

expected_payload = {
'title': post_title,
'raw': DUMMY_MESSAGE,
'category': settings.DISCOURSE_CATEGORY,
"title": f"{DUMMY_TITLE} - {DUMMY_UUID.hex[:8]}",
"raw": DUMMY_MESSAGE,
"category": settings.DISCOURSE_CATEGORY,
}

assert expected_payload == requests_mock.request_history[0].json()
assert status == 200


@patch('joboffers.publishers.discourse.uuid.uuid4')
def test_publish_message_insufficient_title_length(uuid_mock, requests_mock: Mocker, settings):
"""Test that a requests made to the discourse api with a short title."""
requests_mock.post(DISCOURSE_POST_URL, json='', status_code=200)
uuid_mock.return_value = DUMMY_UUID
@patch("joboffers.publishers.discourse.uuid.uuid4", return_value=DUMMY_UUID)
def test_publish_message_insufficient_title_length(uuid_mock, requests_mock, settings):
"""Fix the title being too short."""
settings.DISCOURSE_CATEGORY = DUMMY_CATEGORY
status = DiscoursePublisher()._push_to_api(DUMMY_MESSAGE, DUMMY_TITLE_SHORT, DUMMY_LINK)
settings.DISCOURSE_BASE_URL = "https://localhost:9876"
requests_mock.post("https://localhost:9876/posts.json", json="", status_code=200)

post_title = f'{DUMMY_TITLE_SHORT} - {DUMMY_UUID}'
very_short_title = "A job!"
status = DiscoursePublisher()._push_to_api(DUMMY_MESSAGE, very_short_title, DUMMY_LINK)

expected_payload = {
'title': post_title,
'raw': DUMMY_MESSAGE,
'category': settings.DISCOURSE_CATEGORY,
"title": f"{very_short_title} - {DUMMY_UUID.hex}",
"raw": DUMMY_MESSAGE,
"category": settings.DISCOURSE_CATEGORY,
}

assert expected_payload == requests_mock.request_history[0].json()
assert status == 200


@patch(
'joboffers.publishers.discourse.requests.post',
side_effect=Exception(DUMMY_EXCEPTION_MESSAGE),
)
@patch('joboffers.publishers.discourse.uuid.uuid4', mocked_uuid)
def test_publish_message_generic_error(post_mock, settings, caplog):
"""Test error handling of requests with a general error."""
@patch("joboffers.publishers.discourse.requests.post", side_effect=ValueError("Oops"))
def test_publish_message_request_crash(post_mock, settings, caplog):
"""The request call ended really bad."""
settings.DISCOURSE_CATEGORY = DUMMY_CATEGORY
settings.DISCOURSE_BASE_URL = "https://localhost:9876"

status = DiscoursePublisher()._push_to_api(DUMMY_MESSAGE, DUMMY_TITLE, DUMMY_LINK)
assert status is None

post_title = f'{DUMMY_TITLE} - {DUMMY_UUID[:8]}'
expected_error_message = "Unknown error when publishing: ValueError('Oops')"
assert expected_error_message in caplog.text

payload = {'title': post_title, 'raw': DUMMY_MESSAGE, 'category': DUMMY_CATEGORY}

expected_error_message = ERROR_LOG_MESSAGE % (
DISCOURSE_POST_URL,
payload,
DUMMY_EXCEPTION_MESSAGE,
)
@patch("joboffers.publishers.discourse.uuid.uuid4", return_value=DUMMY_UUID)
def test_publish_message_request_failure(uuid_mock, requests_mock, settings, caplog):
"""The server didn"t return OK."""
settings.DISCOURSE_CATEGORY = DUMMY_CATEGORY
settings.DISCOURSE_BASE_URL = "https://localhost:9876"
requests_mock.post("https://localhost:9876/posts.json", text="Problem!", status_code=500)

status = DiscoursePublisher()._push_to_api(DUMMY_MESSAGE, DUMMY_TITLE, DUMMY_LINK)
assert status == 500

real_title = f"{DUMMY_TITLE} - {DUMMY_UUID.hex[:8]}"
expected_error_message = (
f"Bad server response when publishing: 500 ('Problem!'); "
f"title={real_title!r} message={DUMMY_MESSAGE!r}"
)
assert expected_error_message in caplog.text
assert status is None
2 changes: 1 addition & 1 deletion pyarweb/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@
MASTODON_API_BASE_URL = os.environ.get('MASTODON_API_BASE_URL')

# Discourse constants
DISCOURSE_HOST = os.environ.get('DISCOURSE_HOST')
DISCOURSE_BASE_URL = os.environ.get('DISCOURSE_API_BASE_URL')
DISCOURSE_API_KEY = os.environ.get('DISCOURSE_API_KEY')
DISCOURSE_USERNAME = os.environ.get('DISCOURSE_USERNAME')
DISCOURSE_CATEGORY = os.environ.get('DISCOURSE_CATEGORY')
Expand Down
2 changes: 0 additions & 2 deletions pyarweb/settings/development/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,3 @@

# BASE_URL to use in any notification that might require them
BASE_URL = os.environ.get('BASE_URL', 'http://localhost:8000')

DISCOURSE_HOST = "testdiscourse.com"
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ lxml==4.9.1
Mastodon.py==1.8.1
plotly==5.7.0
psycopg2-binary==2.9.1
requests==2.28.1
tweepy==4.5.0

git+https://github.com/matagus/django-pagination-py3@2fab61163e31685cf8b23b1c768a5263ca03d699

0 comments on commit 722673c

Please sign in to comment.