Add twitter scheduler tests
This commit is contained in:
parent
8c69a22b32
commit
e21c875928
3 changed files with 90 additions and 19 deletions
|
|
@ -0,0 +1,63 @@
|
||||||
|
from json import JSONDecodeError
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from newsreader.accounts.tests.factories import UserFactory
|
||||||
|
from newsreader.news.collection.exceptions import StreamException
|
||||||
|
from newsreader.news.collection.twitter import TwitterTimeLineScheduler
|
||||||
|
|
||||||
|
|
||||||
|
class TwitterTimeLineSchedulerTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
patched_fetch = patch("newsreader.news.collection.twitter.fetch")
|
||||||
|
self.mocked_fetch = patched_fetch.start()
|
||||||
|
|
||||||
|
def test_simple(self):
|
||||||
|
user = UserFactory(twitter_oauth_token="foo", twitter_oauth_token_secret="bar")
|
||||||
|
|
||||||
|
self.mocked_fetch.return_value.json.return_value = {
|
||||||
|
"rate_limit_context": {"application": "dummykey"},
|
||||||
|
"resources": {
|
||||||
|
"statuses": {
|
||||||
|
"/statuses/user_timeline": {
|
||||||
|
"limit": 1500,
|
||||||
|
"remaining": 1500,
|
||||||
|
"reset": 1601141386,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduler = TwitterTimeLineScheduler(user)
|
||||||
|
|
||||||
|
self.assertEquals(scheduler.get_current_ratelimit(), 1500)
|
||||||
|
|
||||||
|
def test_stream_exception(self):
|
||||||
|
user = UserFactory(twitter_oauth_token=None, twitter_oauth_token_secret=None)
|
||||||
|
|
||||||
|
self.mocked_fetch.side_effect = StreamException
|
||||||
|
|
||||||
|
scheduler = TwitterTimeLineScheduler(user)
|
||||||
|
|
||||||
|
self.assertEquals(scheduler.get_current_ratelimit(), None)
|
||||||
|
|
||||||
|
def test_json_decode_error(self):
|
||||||
|
user = UserFactory(twitter_oauth_token="foo", twitter_oauth_token_secret="bar")
|
||||||
|
|
||||||
|
self.mocked_fetch.return_value.json.side_effect = JSONDecodeError(
|
||||||
|
"foo", "bar", 10
|
||||||
|
)
|
||||||
|
|
||||||
|
scheduler = TwitterTimeLineScheduler(user)
|
||||||
|
|
||||||
|
self.assertEquals(scheduler.get_current_ratelimit(), None)
|
||||||
|
|
||||||
|
def test_unexpected_contents(self):
|
||||||
|
user = UserFactory(twitter_oauth_token="foo", twitter_oauth_token_secret="bar")
|
||||||
|
|
||||||
|
self.mocked_fetch.return_value.json.return_value = {"foo": "bar"}
|
||||||
|
|
||||||
|
scheduler = TwitterTimeLineScheduler(user)
|
||||||
|
|
||||||
|
self.assertEquals(scheduler.get_current_ratelimit(), None)
|
||||||
|
|
@ -3,11 +3,13 @@ import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from json import JSONDecodeError
|
from json import JSONDecodeError
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.utils.html import format_html, urlize
|
from django.utils.html import format_html, urlize
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from ftfy import fix_text
|
from ftfy import fix_text
|
||||||
|
from requests_oauthlib import OAuth1 as OAuth
|
||||||
|
|
||||||
from newsreader.news.collection.base import (
|
from newsreader.news.collection.base import (
|
||||||
PostBuilder,
|
PostBuilder,
|
||||||
|
|
@ -166,28 +168,36 @@ class TwitterTimeLineScheduler(Scheduler):
|
||||||
self.user = user
|
self.user = user
|
||||||
|
|
||||||
if not timelines:
|
if not timelines:
|
||||||
self.timelines = user.rules.enabled(
|
self.timelines = (
|
||||||
type=RuleTypeChoices.twitter_timeline
|
user.rules.enabled()
|
||||||
).order_by("last_run")[:200]
|
.filter(type=RuleTypeChoices.twitter_timeline)
|
||||||
|
.order_by("last_run")[:200]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.timelines = timelines
|
self.timelines = timelines
|
||||||
|
|
||||||
def get_scheduled_rules(self):
|
def get_scheduled_rules(self):
|
||||||
if (
|
|
||||||
not self.user.twitter_oauth_token
|
|
||||||
or not self.user.twitter_oauth_token_secret
|
|
||||||
):
|
|
||||||
return []
|
|
||||||
|
|
||||||
max_amount = self.get_current_ratelimit()
|
max_amount = self.get_current_ratelimit()
|
||||||
return self.timelines[:max_amount] if max_amount else []
|
return self.timelines[:max_amount] if max_amount else []
|
||||||
|
|
||||||
def get_current_ratelimit(self):
|
def get_current_ratelimit(self):
|
||||||
endpoint = "application/rate_limit_status.json?resources=statuses"
|
endpoint = "application/rate_limit_status.json?resources=statuses"
|
||||||
|
|
||||||
# TODO add appropriate authentication (OAuth 1.0a) headers
|
if (
|
||||||
|
not self.user.twitter_oauth_token
|
||||||
|
or not self.user.twitter_oauth_token_secret
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
oauth = OAuth(
|
||||||
|
settings.TWITTER_CONSUMER_ID,
|
||||||
|
client_secret=settings.TWITTER_CONSUMER_SECRET,
|
||||||
|
resource_owner_key=self.user.twitter_oauth_token,
|
||||||
|
resource_owner_secret=self.user.twitter_oauth_token_secret,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = fetch(f"{TWITTER_API_URL}/{endpoint}")
|
response = fetch(f"{TWITTER_API_URL}/{endpoint}", auth=oauth)
|
||||||
except StreamException:
|
except StreamException:
|
||||||
logger.exception(f"Unable to retrieve current ratelimit for {self.user.pk}")
|
logger.exception(f"Unable to retrieve current ratelimit for {self.user.pk}")
|
||||||
return
|
return
|
||||||
|
|
@ -198,9 +208,7 @@ class TwitterTimeLineScheduler(Scheduler):
|
||||||
logger.exception(f"Unable to parse ratelimit request for {self.user.pk}")
|
logger.exception(f"Unable to parse ratelimit request for {self.user.pk}")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not "resources" in payload or not "statuses" in payload["resources"]:
|
try:
|
||||||
return []
|
return payload["resources"]["statuses"]["/statuses/user_timeline"]["limit"]
|
||||||
|
except KeyError:
|
||||||
statuses = payload["resources"]["statuses"]
|
return
|
||||||
|
|
||||||
return statuses.get("/statuses/user_timeline", 0)
|
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,12 @@ def build_publication_date(dt, tz):
|
||||||
return published_parsed.astimezone(pytz.utc)
|
return published_parsed.astimezone(pytz.utc)
|
||||||
|
|
||||||
|
|
||||||
def fetch(url, headers={}):
|
def fetch(url, auth=None, headers={}):
|
||||||
headers = {**DEFAULT_HEADERS, **headers}
|
headers = {**DEFAULT_HEADERS, **headers}
|
||||||
|
|
||||||
with ResponseHandler() as response_handler:
|
with ResponseHandler() as response_handler:
|
||||||
try:
|
try:
|
||||||
response = requests.get(url, headers=headers)
|
response = requests.get(url, auth=auth, headers=headers)
|
||||||
response_handler.handle_response(response)
|
response_handler.handle_response(response)
|
||||||
except RequestException as exception:
|
except RequestException as exception:
|
||||||
response_handler.map_exception(exception)
|
response_handler.map_exception(exception)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue