0.3.13.7
This commit is contained in:
parent
8498303006
commit
510f7187a8
6 changed files with 108 additions and 47 deletions
|
|
@ -1,5 +1,9 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.3.13.7
|
||||||
|
|
||||||
|
- Check for Twitter error codes in response
|
||||||
|
|
||||||
## 0.3.13.6
|
## 0.3.13.6
|
||||||
|
|
||||||
- Try to load sentry by default for all environments
|
- Try to load sentry by default for all environments
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "newsreader",
|
"name": "newsreader",
|
||||||
"version": "0.3.13.6",
|
"version": "0.3.13.7",
|
||||||
"description": "Application for viewing RSS feeds",
|
"description": "Application for viewing RSS feeds",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "newsreader"
|
name = "newsreader"
|
||||||
version = "0.3.13.6"
|
version = "0.3.13.7"
|
||||||
description = "Webapplication for reading RSS feeds"
|
description = "Webapplication for reading RSS feeds"
|
||||||
authors = ["Sonny <sonnyba871@gmail.com>"]
|
authors = ["Sonny <sonnyba871@gmail.com>"]
|
||||||
license = "GPL-3.0"
|
license = "GPL-3.0"
|
||||||
|
|
|
||||||
|
|
@ -74,19 +74,24 @@ class TwitterClientTestCase(TestCase):
|
||||||
self.mocked_read.assert_called()
|
self.mocked_read.assert_called()
|
||||||
|
|
||||||
def test_client_catches_stream_denied_exception(self):
|
def test_client_catches_stream_denied_exception(self):
|
||||||
|
"""
|
||||||
|
Twitter also returns these responses for accounts which have been shutdown.
|
||||||
|
Therefore the error codes should be checked inside the response body.
|
||||||
|
See https://stackoverflow.com/questions/8357568/do-twitter-access-token-expire
|
||||||
|
"""
|
||||||
user = UserFactory(
|
user = UserFactory(
|
||||||
twitter_oauth_token=str(uuid4()), twitter_oauth_token_secret=str(uuid4())
|
twitter_oauth_token=str(uuid4()), twitter_oauth_token_secret=str(uuid4())
|
||||||
)
|
)
|
||||||
timeline = TwitterTimelineFactory(user=user)
|
timeline = TwitterTimelineFactory(user=user)
|
||||||
|
|
||||||
self.mocked_read.side_effect = StreamDeniedException(message="Token expired")
|
self.mocked_read.side_effect = StreamDeniedException(message="Not authorized")
|
||||||
|
|
||||||
with TwitterClient([timeline]) as client:
|
with TwitterClient([timeline]) as client:
|
||||||
for data, stream in client:
|
for data, stream in client:
|
||||||
with self.subTest(data=data, stream=stream):
|
with self.subTest(data=data, stream=stream):
|
||||||
self.assertIsNone(data)
|
self.assertIsNone(data)
|
||||||
self.assertIsNone(stream)
|
self.assertIsNone(stream)
|
||||||
self.assertEquals(stream.rule.error, "Token expired")
|
self.assertEquals(stream.rule.error, "Authorization required")
|
||||||
self.assertEquals(stream.rule.succeeded, False)
|
self.assertEquals(stream.rule.succeeded, False)
|
||||||
|
|
||||||
self.mocked_read.assert_called()
|
self.mocked_read.assert_called()
|
||||||
|
|
@ -94,8 +99,8 @@ class TwitterClientTestCase(TestCase):
|
||||||
user.refresh_from_db()
|
user.refresh_from_db()
|
||||||
timeline.refresh_from_db()
|
timeline.refresh_from_db()
|
||||||
|
|
||||||
self.assertIsNone(user.twitter_oauth_token)
|
self.assertIsNotNone(user.twitter_oauth_token)
|
||||||
self.assertIsNone(user.twitter_oauth_token_secret)
|
self.assertIsNotNone(user.twitter_oauth_token_secret)
|
||||||
|
|
||||||
def test_client_catches_stream_timed_out_exception(self):
|
def test_client_catches_stream_timed_out_exception(self):
|
||||||
timeline = TwitterTimelineFactory()
|
timeline = TwitterTimelineFactory()
|
||||||
|
|
@ -160,3 +165,31 @@ class TwitterClientTestCase(TestCase):
|
||||||
self.assertEquals(stream.rule.succeeded, False)
|
self.assertEquals(stream.rule.succeeded, False)
|
||||||
|
|
||||||
self.mocked_read.assert_called()
|
self.mocked_read.assert_called()
|
||||||
|
|
||||||
|
def test_client_catches_token_expired(self):
|
||||||
|
user = UserFactory(
|
||||||
|
twitter_oauth_token=str(uuid4()), twitter_oauth_token_secret=str(uuid4())
|
||||||
|
)
|
||||||
|
timeline = TwitterTimelineFactory(user=user)
|
||||||
|
|
||||||
|
response = Mock(json=lambda: {"errors": [{"code": 89}]})
|
||||||
|
|
||||||
|
self.mocked_read.side_effect = StreamDeniedException(
|
||||||
|
message="Not authorized", response=response
|
||||||
|
)
|
||||||
|
|
||||||
|
with TwitterClient([timeline]) as client:
|
||||||
|
for data, stream in client:
|
||||||
|
with self.subTest(data=data, stream=stream):
|
||||||
|
self.assertIsNone(data)
|
||||||
|
self.assertIsNone(stream)
|
||||||
|
self.assertEquals(stream.rule.error, "Authorization required")
|
||||||
|
self.assertEquals(stream.rule.succeeded, False)
|
||||||
|
|
||||||
|
self.mocked_read.assert_called()
|
||||||
|
|
||||||
|
user.refresh_from_db()
|
||||||
|
timeline.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertIsNone(user.twitter_oauth_token)
|
||||||
|
self.assertIsNone(user.twitter_oauth_token_secret)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from unittest.mock import patch
|
from unittest.mock import Mock, patch
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
@ -152,8 +152,8 @@ class TwitterCollectorTestCase(TestCase):
|
||||||
|
|
||||||
user = timeline.user
|
user = timeline.user
|
||||||
|
|
||||||
self.assertIsNone(user.twitter_oauth_token)
|
self.assertIsNotNone(user.twitter_oauth_token)
|
||||||
self.assertIsNone(user.twitter_oauth_token_secret)
|
self.assertIsNotNone(user.twitter_oauth_token_secret)
|
||||||
|
|
||||||
def test_forbidden(self):
|
def test_forbidden(self):
|
||||||
self.mocked_fetch.side_effect = StreamForbiddenException
|
self.mocked_fetch.side_effect = StreamForbiddenException
|
||||||
|
|
@ -178,3 +178,25 @@ class TwitterCollectorTestCase(TestCase):
|
||||||
self.assertEquals(Post.objects.count(), 0)
|
self.assertEquals(Post.objects.count(), 0)
|
||||||
self.assertEquals(timeline.succeeded, False)
|
self.assertEquals(timeline.succeeded, False)
|
||||||
self.assertEquals(timeline.error, "Stream timed out")
|
self.assertEquals(timeline.error, "Stream timed out")
|
||||||
|
|
||||||
|
def test_token_expired(self):
|
||||||
|
response = Mock(json=lambda: {"errors": [{"code": 89}]})
|
||||||
|
|
||||||
|
self.mocked_fetch.side_effect = StreamDeniedException(response=response)
|
||||||
|
|
||||||
|
timeline = TwitterTimelineFactory(
|
||||||
|
user__twitter_oauth_token=str(uuid4()),
|
||||||
|
user__twitter_oauth_token_secret=str(uuid4()),
|
||||||
|
)
|
||||||
|
|
||||||
|
collector = TwitterCollector()
|
||||||
|
collector.collect(rules=[timeline])
|
||||||
|
|
||||||
|
self.assertEquals(Post.objects.count(), 0)
|
||||||
|
self.assertEquals(timeline.succeeded, False)
|
||||||
|
self.assertEquals(timeline.error, "Stream does not have sufficient permissions")
|
||||||
|
|
||||||
|
user = timeline.user
|
||||||
|
|
||||||
|
self.assertIsNone(user.twitter_oauth_token)
|
||||||
|
self.assertIsNone(user.twitter_oauth_token_secret)
|
||||||
|
|
|
||||||
|
|
@ -232,44 +232,6 @@ class TwitterClient(PostClient):
|
||||||
|
|
||||||
self.set_rule_error(stream.rule, e)
|
self.set_rule_error(stream.rule, e)
|
||||||
|
|
||||||
break
|
|
||||||
except StreamDeniedException as e:
|
|
||||||
logger.warning(
|
|
||||||
f"Access token expired for user {stream.rule.user.pk}"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
import sentry_sdk
|
|
||||||
|
|
||||||
with sentry_sdk.push_scope() as scope:
|
|
||||||
scope.set_extra(
|
|
||||||
"content", e.response.content if e.response else None
|
|
||||||
)
|
|
||||||
sentry_sdk.capture_message(
|
|
||||||
"Twitter authentication credentials reset"
|
|
||||||
)
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
stream.rule.user.twitter_oauth_token = None
|
|
||||||
stream.rule.user.twitter_oauth_token_secret = None
|
|
||||||
stream.rule.user.save()
|
|
||||||
|
|
||||||
message = _(
|
|
||||||
"Your Twitter account credentials have expired. Re-authenticate in"
|
|
||||||
" the settings page to keep retrieving Twitter specific information"
|
|
||||||
" from your account."
|
|
||||||
)
|
|
||||||
|
|
||||||
send_mail(
|
|
||||||
"Twitter account needs re-authentication",
|
|
||||||
message,
|
|
||||||
None,
|
|
||||||
[stream.rule.user.email],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.set_rule_error(stream.rule, e)
|
|
||||||
|
|
||||||
break
|
break
|
||||||
except (StreamNotFoundException, StreamTimeOutException) as e:
|
except (StreamNotFoundException, StreamTimeOutException) as e:
|
||||||
logger.warning(f"Request failed for {stream.rule.screen_name}")
|
logger.warning(f"Request failed for {stream.rule.screen_name}")
|
||||||
|
|
@ -282,6 +244,46 @@ class TwitterClient(PostClient):
|
||||||
|
|
||||||
self.set_rule_error(stream.rule, e)
|
self.set_rule_error(stream.rule, e)
|
||||||
|
|
||||||
|
if not e.response:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
response_data = e.response.json()
|
||||||
|
except JSONDecodeError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "errors" in response_data:
|
||||||
|
errors = response_data["errors"]
|
||||||
|
token_expired = any(error["code"] == 89 for error in errors)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import sentry_sdk
|
||||||
|
|
||||||
|
with sentry_sdk.push_scope() as scope:
|
||||||
|
scope.set_extra("content", response_data)
|
||||||
|
sentry_sdk.capture_message(
|
||||||
|
"Twitter authentication credentials reset"
|
||||||
|
)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
stream.rule.user.twitter_oauth_token = None
|
||||||
|
stream.rule.user.twitter_oauth_token_secret = None
|
||||||
|
stream.rule.user.save()
|
||||||
|
|
||||||
|
message = _(
|
||||||
|
"Your Twitter account credentials have expired. Re-authenticate in"
|
||||||
|
" the settings page to keep retrieving Twitter specific information"
|
||||||
|
" from your account."
|
||||||
|
)
|
||||||
|
|
||||||
|
send_mail(
|
||||||
|
"Twitter account needs re-authentication",
|
||||||
|
message,
|
||||||
|
None,
|
||||||
|
[stream.rule.user.email],
|
||||||
|
)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
finally:
|
finally:
|
||||||
stream.rule.last_run = timezone.now()
|
stream.rule.last_run = timezone.now()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue