Use Idempotency-Key header when posting toots

This commit is contained in:
Ivan Habunek 2018-06-13 13:22:52 +02:00
parent 8f93b255ad
commit 025d8dde09
No known key found for this signature in database
GPG key ID: CDBD63C43A30BB95
4 changed files with 24 additions and 7 deletions

View file

@ -4,6 +4,8 @@ Changelog
**0.19.0 (TBA)** **0.19.0 (TBA)**
* Add support for replying to a toot (#6) * Add support for replying to a toot (#6)
* Use Idempotency-Key header to prevent multiple toots being posted if request
is retried
**0.18.0 (2018-06-12)** **0.18.0 (2018-06-12)**

View file

@ -2,7 +2,9 @@
import io import io
import pytest import pytest
import re import re
import uuid
from collections import namedtuple
from unittest import mock from unittest import mock
from toot import console, User, App, http from toot import console, User, App, http
@ -13,6 +15,8 @@ from tests.utils import MockResponse
app = App('habunek.com', 'https://habunek.com', 'foo', 'bar') app = App('habunek.com', 'https://habunek.com', 'foo', 'bar')
user = User('habunek.com', 'ivan@habunek.com', 'xxx') user = User('habunek.com', 'ivan@habunek.com', 'xxx')
MockUuid = namedtuple("MockUuid", ["hex"])
def uncolorize(text): def uncolorize(text):
"""Remove ANSI color sequences from a string""" """Remove ANSI color sequences from a string"""
@ -25,8 +29,10 @@ def test_print_usage(capsys):
assert "toot - a Mastodon CLI client" in out assert "toot - a Mastodon CLI client" in out
@mock.patch('uuid.uuid4')
@mock.patch('toot.http.post') @mock.patch('toot.http.post')
def test_post_defaults(mock_post, capsys): def test_post_defaults(mock_post, mock_uuid, capsys):
mock_uuid.return_value = MockUuid("rock-on")
mock_post.return_value = MockResponse({ mock_post.return_value = MockResponse({
'url': 'https://habunek.com/@ihabunek/1234567890' 'url': 'https://habunek.com/@ihabunek/1234567890'
}) })
@ -40,7 +46,7 @@ def test_post_defaults(mock_post, capsys):
'sensitive': False, 'sensitive': False,
'spoiler_text': None, 'spoiler_text': None,
'in_reply_to_id': None, 'in_reply_to_id': None,
}) }, headers={"Idempotency-Key": "rock-on"})
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert 'Toot posted' in out assert 'Toot posted' in out
@ -48,8 +54,10 @@ def test_post_defaults(mock_post, capsys):
assert not err assert not err
@mock.patch('uuid.uuid4')
@mock.patch('toot.http.post') @mock.patch('toot.http.post')
def test_post_with_options(mock_post, capsys): def test_post_with_options(mock_post, mock_uuid, capsys):
mock_uuid.return_value = MockUuid("up-the-irons")
args = [ args = [
'Hello world', 'Hello world',
'--visibility', 'unlisted', '--visibility', 'unlisted',
@ -71,7 +79,7 @@ def test_post_with_options(mock_post, capsys):
'sensitive': True, 'sensitive': True,
'spoiler_text': "Spoiler!", 'spoiler_text': "Spoiler!",
'in_reply_to_id': 123, 'in_reply_to_id': 123,
}) }, headers={"Idempotency-Key": "up-the-irons"})
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert 'Toot posted' in out assert 'Toot posted' in out

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import re import re
import uuid
from urllib.parse import urlparse, urlencode, quote from urllib.parse import urlparse, urlencode, quote
@ -90,6 +91,11 @@ def post_status(
Posts a new status. Posts a new status.
https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#posting-a-new-status https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#posting-a-new-status
""" """
# Idempotency key assures the same status is not posted multiple times
# if the request is retried.
headers = {"Idempotency-Key": uuid.uuid4().hex}
return http.post(app, user, '/api/v1/statuses', { return http.post(app, user, '/api/v1/statuses', {
'status': status, 'status': status,
'media_ids[]': media_ids, 'media_ids[]': media_ids,
@ -97,7 +103,7 @@ def post_status(
'sensitive': sensitive, 'sensitive': sensitive,
'spoiler_text': spoiler_text, 'spoiler_text': spoiler_text,
'in_reply_to_id': in_reply_to_id, 'in_reply_to_id': in_reply_to_id,
}).json() }, headers=headers).json()
def timeline_home(app, user): def timeline_home(app, user):

View file

@ -58,9 +58,10 @@ def anon_get(url, params=None):
return process_response(response) return process_response(response)
def post(app, user, url, data=None, files=None, allow_redirects=True): def post(app, user, url, data=None, files=None, allow_redirects=True, headers={}):
url = app.base_url + url url = app.base_url + url
headers = {"Authorization": "Bearer " + user.access_token}
headers["Authorization"] = "Bearer " + user.access_token
request = Request('POST', url, headers, files, data) request = Request('POST', url, headers, files, data)
response = send_request(request, allow_redirects) response = send_request(request, allow_redirects)