Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 22bf3b088e | |||
| 811453a129 | |||
| f8d628909c | |||
| 507e377a37 | |||
| 0aac8f6628 | |||
| acdcb03283 | |||
| 6bd73fdd5c | |||
| c13b456b79 | |||
| 977885967d | |||
| b9066ca637 | |||
| 77254f441c | |||
| 66089011f7 | |||
| fe317b6be8 | |||
| 15e82aacd1 | |||
| 008f21d6fe | |||
| 6baf51bbe1 | |||
| 8304e7b2f5 | |||
| 5806beef34 | |||
| f905a957f4 | |||
| 06ca2c5a58 | |||
| bde4325776 | |||
| 6b7e570475 | |||
| 3659724f11 | |||
| 1e151fc51f | |||
| 63f120ddca | |||
| 9cb96eaf76 | |||
| c5207759a8 | |||
| b14cd502ce | |||
| 3a3bb843e5 | |||
| a2f961aebb |
@@ -1,2 +1,9 @@
|
||||
(Based on original authors list and may be incomplete)
|
||||
|
||||
Marc Sibson
|
||||
James Potter
|
||||
Ben Lopatin
|
||||
Daniel Zohar
|
||||
Matheus Fernandes
|
||||
Scott Nixon
|
||||
Jason Dorweiler
|
||||
|
||||
+38
-6
@@ -1,14 +1,46 @@
|
||||
=========
|
||||
Changelog
|
||||
=========
|
||||
.. :changelog:
|
||||
|
||||
Release history
|
||||
===============
|
||||
|
||||
0.7.0
|
||||
-----
|
||||
|
||||
* Place request parameters in the request body for POST and PUT requests.
|
||||
Allows larger request sizes and solves for `URI Too Large` error.
|
||||
|
||||
0.6.0
|
||||
-----
|
||||
|
||||
* Adds method to add user to group by user ID
|
||||
|
||||
0.5.0
|
||||
-----
|
||||
|
||||
* Adds badges functionality
|
||||
|
||||
0.4.0
|
||||
-----
|
||||
|
||||
* Adds initial groups functionality
|
||||
|
||||
0.3.2
|
||||
-----
|
||||
|
||||
* SSO functionality fixes
|
||||
|
||||
0.3.1
|
||||
-----
|
||||
|
||||
* Fix how empty responses are handled
|
||||
|
||||
0.3.0
|
||||
=====
|
||||
-----
|
||||
|
||||
* Added method to unsuspend suspended user
|
||||
|
||||
0.2.0
|
||||
=====
|
||||
-----
|
||||
|
||||
* Inital fork, including gberaudo's changes
|
||||
* Packaging cleanup, dropping Python 2.6 support and adding Python 3.5, PyPy,
|
||||
@@ -16,7 +48,7 @@ Changelog
|
||||
* Packaging on PyPI
|
||||
|
||||
0.1.0.dev
|
||||
=========
|
||||
---------
|
||||
|
||||
All pre-PyPI development
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
include setup.py
|
||||
include README.rst
|
||||
include MANIFEST.in
|
||||
include HISTORY.rst
|
||||
include LICENSE
|
||||
recursive-include pydiscourse
|
||||
@@ -37,18 +37,25 @@ test-all: ## Run all tox test environments, parallelized
|
||||
|
||||
check: clean-build clean-pyc clean-test lint test-coverage
|
||||
|
||||
release: clean ## Uploads new source and wheel distributions (cleans first)
|
||||
python setup.py sdist upload
|
||||
python setup.py bdist_wheel upload
|
||||
build: clean ## Create distribution files for release
|
||||
python setup.py sdist bdist_wheel
|
||||
|
||||
dist: clean ## Creates new source and wheel distributions (cleans first)
|
||||
release: build ## Create distribution files and publish to PyPI
|
||||
python setup.py check -r -s
|
||||
twine upload dist/*
|
||||
|
||||
sdist: clean ##sdist Create source distribution only
|
||||
python setup.py sdist
|
||||
python setup.py bdist_wheel
|
||||
ls -l dist
|
||||
|
||||
docs: ## Builds and open docs
|
||||
api-docs: ## Build autodocs from docstrings
|
||||
sphinx-apidoc -f -o docs pydiscourse
|
||||
|
||||
manual-docs: ## Build written docs
|
||||
$(MAKE) -C docs clean
|
||||
$(MAKE) -C docs html
|
||||
|
||||
docs: api-docs manual-docs ## Builds and open docs
|
||||
open docs/_build/html/index.html
|
||||
|
||||
help:
|
||||
|
||||
+13
-3
@@ -14,17 +14,27 @@ additional functionality, and to distribute a package on PyPI.
|
||||
Goals
|
||||
=====
|
||||
|
||||
* Exceptional documentation
|
||||
* Support all supported Python versions
|
||||
* Provide functional parity with the Discourse API, for the currently supported
|
||||
version of Discourse (something of a moving target)
|
||||
* Support all supported Python versions
|
||||
* Document API
|
||||
|
||||
The order here is important. The Discourse API is itself poorly documented so
|
||||
the level of documentation in the Python client is critical.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
::
|
||||
|
||||
pip install pydiscourse
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Create a client connection to a Discourse server::
|
||||
|
||||
from pydiscourse.client import DiscourseClient
|
||||
from pydiscourse import DiscourseClient
|
||||
client = DiscourseClient(
|
||||
'http://example.com',
|
||||
api_username='username',
|
||||
|
||||
+3
-5
@@ -28,9 +28,7 @@ import os
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
]
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
@@ -53,9 +51,9 @@ copyright = u'2014, Marc Sibson'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.3'
|
||||
version = '0.7'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.3.0'
|
||||
release = '0.7.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
pydiscourse
|
||||
===========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
pydiscourse
|
||||
@@ -0,0 +1,46 @@
|
||||
pydiscourse package
|
||||
===================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
pydiscourse.client module
|
||||
-------------------------
|
||||
|
||||
.. automodule:: pydiscourse.client
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
pydiscourse.exceptions module
|
||||
-----------------------------
|
||||
|
||||
.. automodule:: pydiscourse.exceptions
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
pydiscourse.main module
|
||||
-----------------------
|
||||
|
||||
.. automodule:: pydiscourse.main
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
pydiscourse.sso module
|
||||
----------------------
|
||||
|
||||
.. automodule:: pydiscourse.sso
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: pydiscourse
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1 +1,5 @@
|
||||
__version__ = '0.3.0'
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__version__ = '0.7.0'
|
||||
|
||||
from pydiscourse.client import DiscourseClient
|
||||
|
||||
+193
-16
@@ -13,6 +13,12 @@ from pydiscourse.sso import sso_payload
|
||||
|
||||
log = logging.getLogger('pydiscourse.client')
|
||||
|
||||
# HTTP verbs to be used as non string literals
|
||||
DELETE = "DELETE"
|
||||
GET = "GET"
|
||||
POST = "POST"
|
||||
PUT = "PUT"
|
||||
|
||||
|
||||
class DiscourseClient(object):
|
||||
"""Discourse API client"""
|
||||
@@ -127,7 +133,8 @@ class DiscourseClient(object):
|
||||
????
|
||||
|
||||
"""
|
||||
return self._put('/admin/users/{0}/suspend'.format(userid), duration=duration, reason=reason)
|
||||
return self._put('/admin/users/{0}/suspend'.format(userid),
|
||||
duration=duration, reason=reason)
|
||||
|
||||
def unsuspend(self, userid):
|
||||
"""
|
||||
@@ -251,7 +258,8 @@ class DiscourseClient(object):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self._put('/users/{0}/preferences/username'.format(username), username=new_username, **kwargs)
|
||||
return self._put('/users/{0}/preferences/username'.format(username),
|
||||
username=new_username, **kwargs)
|
||||
|
||||
def set_preference(self, username=None, **kwargs):
|
||||
"""
|
||||
@@ -270,13 +278,15 @@ class DiscourseClient(object):
|
||||
def sync_sso(self, **kwargs):
|
||||
"""
|
||||
|
||||
expect sso_secret, name, username, email, external_id, avatar_url,
|
||||
avatar_force_update
|
||||
|
||||
Args:
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
# expect sso_secret, name, username, email, external_id, avatar_url, avatar_force_update
|
||||
sso_secret = kwargs.pop('sso_secret')
|
||||
payload = sso_payload(sso_secret, **kwargs)
|
||||
return self._post('/admin/users/sync_sso?{0}'.format(payload), **kwargs)
|
||||
@@ -531,7 +541,45 @@ class DiscourseClient(object):
|
||||
kwargs['term'] = term
|
||||
return self._get('/search.json', **kwargs)
|
||||
|
||||
def create_category(self, name, color, text_color='FFFFFF', permissions=None, parent=None, **kwargs):
|
||||
|
||||
def badges(self, **kwargs):
|
||||
"""
|
||||
|
||||
Args:
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self._get('/admin/badges.json', **kwargs)
|
||||
|
||||
def grant_badge_to(self, username, badge_id, **kwargs):
|
||||
"""
|
||||
|
||||
Args:
|
||||
username:
|
||||
badge_id:
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self._post('/user_badges', username=username, badge_id=badge_id, **kwargs)
|
||||
|
||||
def user_badges(self, username, **kwargs):
|
||||
"""
|
||||
|
||||
Args:
|
||||
username:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self._get('/user-badges/{}.json'.format(username))
|
||||
|
||||
|
||||
def create_category(self, name, color, text_color='FFFFFF',
|
||||
permissions=None, parent=None, **kwargs):
|
||||
"""
|
||||
|
||||
Args:
|
||||
@@ -606,7 +654,134 @@ class DiscourseClient(object):
|
||||
"""
|
||||
for setting, value in kwargs.items():
|
||||
setting = setting.replace(' ', '_')
|
||||
self._request('PUT', '/admin/site_settings/{0}'.format(setting), {setting: value})
|
||||
self._request(PUT, '/admin/site_settings/{0}'.format(setting), {setting: value})
|
||||
|
||||
def groups(self, **kwargs):
|
||||
"""
|
||||
Returns a list of all groups.
|
||||
|
||||
Returns:
|
||||
List of dictionaries of groups
|
||||
|
||||
[
|
||||
{
|
||||
'alias_level': 0,
|
||||
'automatic': True,
|
||||
'automatic_membership_email_domains': None,
|
||||
'automatic_membership_retroactive': False,
|
||||
'grant_trust_level': None,
|
||||
'has_messages': True,
|
||||
'id': 1,
|
||||
'incoming_email': None,
|
||||
'mentionable': False,
|
||||
'name': 'admins',
|
||||
'notification_level': 2,
|
||||
'primary_group': False,
|
||||
'title': None,
|
||||
'user_count': 9,
|
||||
'visible': True
|
||||
},
|
||||
{
|
||||
'alias_level': 0,
|
||||
'automatic': True,
|
||||
'automatic_membership_email_domains': None,
|
||||
'automatic_membership_retroactive': False,
|
||||
'grant_trust_level': None,
|
||||
'has_messages': False,
|
||||
'id': 0,
|
||||
'incoming_email': None,
|
||||
'mentionable': False,
|
||||
'name': 'everyone',
|
||||
'notification_level': None,
|
||||
'primary_group': False,
|
||||
'title': None,
|
||||
'user_count': 0,
|
||||
'visible': True
|
||||
}
|
||||
]
|
||||
|
||||
"""
|
||||
return self._get("/admin/groups.json", **kwargs)
|
||||
|
||||
def add_group_owner(self, groupid, username):
|
||||
"""
|
||||
Add an owner to a group by username
|
||||
|
||||
Args:
|
||||
groupid: the ID of the group
|
||||
username: the new owner usernmae
|
||||
|
||||
Returns:
|
||||
JSON API response
|
||||
|
||||
"""
|
||||
return self._put("/admin/groups/{0}/owners.json".format(groupid), usernames=username)
|
||||
|
||||
def delete_group_owner(self, groupid, userid):
|
||||
"""
|
||||
Deletes an owner from a group by user ID
|
||||
|
||||
Does not delete the user from Discourse.
|
||||
|
||||
Args:
|
||||
groupid: the ID of the group
|
||||
userid: the ID of the user
|
||||
|
||||
Returns:
|
||||
JSON API response
|
||||
|
||||
"""
|
||||
return self._delete("/admin/groups/{0}/owners.json".format(groupid), user_id=userid)
|
||||
|
||||
def add_group_member(self, groupid, username):
|
||||
"""
|
||||
Add a member to a group by username
|
||||
|
||||
Args:
|
||||
groupid: the ID of the group
|
||||
username: the new member usernmae
|
||||
|
||||
Returns:
|
||||
JSON API response
|
||||
|
||||
Raises:
|
||||
DiscourseError if user is already member of group
|
||||
|
||||
"""
|
||||
return self._put("/admin/groups/{0}/members.json".format(groupid), usernames=username)
|
||||
|
||||
def add_user_to_group(self, groupid, userid):
|
||||
"""
|
||||
Add a member to a group by with user id.
|
||||
|
||||
Args:
|
||||
groupid: the ID of the group
|
||||
userid: the member id
|
||||
|
||||
Returns:
|
||||
JSON API response
|
||||
|
||||
Raises:
|
||||
DiscourseError if user is already member of group
|
||||
|
||||
"""
|
||||
return self._post("/admin/users/{0}/groups".format(userid), group_id=groupid)
|
||||
|
||||
def delete_group_member(self, groupid, userid):
|
||||
"""
|
||||
Deletes a member from a group by user ID
|
||||
|
||||
Does not delete the user from Discourse.
|
||||
|
||||
Args:
|
||||
groupid: the ID of the group
|
||||
userid: the ID of the user
|
||||
|
||||
Returns:
|
||||
JSON API response
|
||||
|
||||
"""
|
||||
return self._delete("/admin/groups/{0}/members.json".format(groupid), user_id=userid)
|
||||
|
||||
def _get(self, path, **kwargs):
|
||||
"""
|
||||
@@ -618,7 +793,7 @@ class DiscourseClient(object):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self._request('GET', path, kwargs)
|
||||
return self._request(GET, path, params=kwargs)
|
||||
|
||||
def _put(self, path, **kwargs):
|
||||
"""
|
||||
@@ -630,7 +805,7 @@ class DiscourseClient(object):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self._request('PUT', path, kwargs)
|
||||
return self._request(PUT, path, data=kwargs)
|
||||
|
||||
def _post(self, path, **kwargs):
|
||||
"""
|
||||
@@ -642,7 +817,7 @@ class DiscourseClient(object):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self._request('POST', path, kwargs)
|
||||
return self._request(POST, path, data=kwargs)
|
||||
|
||||
def _delete(self, path, **kwargs):
|
||||
"""
|
||||
@@ -654,15 +829,16 @@ class DiscourseClient(object):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self._request('DELETE', path, kwargs)
|
||||
return self._request(DELETE, path, params=kwargs)
|
||||
|
||||
def _request(self, verb, path, params):
|
||||
def _request(self, verb, path, params={}, data={}):
|
||||
"""
|
||||
Executes HTTP request to API and handles response
|
||||
|
||||
Args:
|
||||
verb:
|
||||
path:
|
||||
params:
|
||||
verb: HTTP verb as string: GET, DELETE, PUT, POST
|
||||
path: the path on the Discourse API
|
||||
params: dictionary of parameters to include to the API
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -675,7 +851,7 @@ class DiscourseClient(object):
|
||||
headers = {'Accept': 'application/json; charset=utf-8'}
|
||||
|
||||
response = requests.request(
|
||||
verb, url, allow_redirects=False, params=params, headers=headers,
|
||||
verb, url, allow_redirects=False, params=params, data=data, headers=headers,
|
||||
timeout=self.timeout)
|
||||
|
||||
log.debug('response %s: %s', response.status_code, repr(response.text))
|
||||
@@ -694,13 +870,14 @@ class DiscourseClient(object):
|
||||
raise DiscourseServerError(msg, response=response)
|
||||
|
||||
if response.status_code == 302:
|
||||
raise DiscourseError('Unexpected Redirect, invalid api key or host?', response=response)
|
||||
raise DiscourseError(
|
||||
'Unexpected Redirect, invalid api key or host?', response=response)
|
||||
|
||||
json_content = 'application/json; charset=utf-8'
|
||||
content_type = response.headers['content-type']
|
||||
if content_type != json_content:
|
||||
# some calls return empty html documents
|
||||
if response.content == ' ':
|
||||
if not response.content.strip():
|
||||
return None
|
||||
|
||||
raise DiscourseError('Invalid Response, expecting "{0}" got "{1}"'.format(
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ class DiscourseCmd(cmd.Cmd):
|
||||
try:
|
||||
return method(*args, **kwargs)
|
||||
except DiscourseError as e:
|
||||
print (e, e.response.text)
|
||||
print(e, e.response.text)
|
||||
return e.response
|
||||
return wrapper
|
||||
|
||||
|
||||
+11
-7
@@ -1,9 +1,11 @@
|
||||
"""
|
||||
Utilities to implement Single Sign On for Discourse with a Python managed authentication DB
|
||||
Utilities to implement Single Sign On for Discourse with a Python managed
|
||||
authentication DB
|
||||
|
||||
https://meta.discourse.org/t/official-single-sign-on-for-discourse/13045
|
||||
|
||||
Thanks to James Potter for the heavy lifting, detailed at https://meta.discourse.org/t/sso-example-for-django/14258
|
||||
Thanks to James Potter for the heavy lifting, detailed at
|
||||
https://meta.discourse.org/t/sso-example-for-django/14258
|
||||
|
||||
A SSO request handler might look something like
|
||||
|
||||
@@ -16,7 +18,8 @@ A SSO request handler might look something like
|
||||
except DiscourseError as e:
|
||||
return HTTP400(e.args[0])
|
||||
|
||||
url = sso_redirect_url(nonce, SECRET, request.user.email, request.user.id, request.user.username)
|
||||
url = sso_redirect_url(nonce, SECRET, request.user.email,
|
||||
request.user.id, request.user.username)
|
||||
return redirect('http://discuss.example.com' + url)
|
||||
"""
|
||||
from base64 import b64encode, b64decode
|
||||
@@ -24,9 +27,10 @@ import hmac
|
||||
import hashlib
|
||||
|
||||
try: # py3
|
||||
from urllib.parse import unquote, urlencode
|
||||
from urllib.parse import unquote, urlencode, parse_qs
|
||||
except ImportError:
|
||||
from urllib import unquote, urlencode
|
||||
from urlparse import parse_qs
|
||||
|
||||
|
||||
from pydiscourse.exceptions import DiscourseError
|
||||
@@ -60,9 +64,9 @@ def sso_validate(payload, signature, secret):
|
||||
if this_signature != signature:
|
||||
raise DiscourseError('Payload does not match signature.')
|
||||
|
||||
nonce = decoded.split('=')[1]
|
||||
|
||||
return nonce
|
||||
# Discourse returns querystring encoded value. We only need `nonce`
|
||||
qs = parse_qs(decoded)
|
||||
return qs['nonce'][0]
|
||||
|
||||
|
||||
def sso_payload(secret, **kwargs):
|
||||
|
||||
@@ -2,21 +2,31 @@ from setuptools import setup, find_packages
|
||||
|
||||
|
||||
README = open('README.rst').read()
|
||||
VERSION = __import__("pydiscourse").__version__
|
||||
HISTORY = open('HISTORY.rst').read().replace('.. :changelog:', '')
|
||||
|
||||
with open("pydiscourse/__init__.py", "r") as module_file:
|
||||
for line in module_file:
|
||||
if line.startswith("__version__"):
|
||||
version_string = line.split("=")[1]
|
||||
VERSION = version_string.strip().replace("'", "")
|
||||
|
||||
|
||||
setup(
|
||||
name="pydiscourse",
|
||||
version=VERSION,
|
||||
description="A Python library for the Discourse API",
|
||||
long_description=README,
|
||||
long_description=README + '\n\n' + HISTORY,
|
||||
author="Marc Sibson and contributors",
|
||||
author_email="ben+pydiscourse@benlopatin.com",
|
||||
license="BSD",
|
||||
url="https://github.com/bennylope/pydiscourse",
|
||||
packages=find_packages(exclude=["tests.*", "tests"]),
|
||||
install_requires=['requests>=2.0.0'],
|
||||
tests_require=['mock'],
|
||||
install_requires=[
|
||||
'requests>=2.0.0',
|
||||
],
|
||||
tests_require=[
|
||||
'mock',
|
||||
],
|
||||
test_suite='tests',
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
|
||||
+55
-2
@@ -1,8 +1,18 @@
|
||||
import sys
|
||||
import unittest
|
||||
import mock
|
||||
|
||||
from pydiscourse import client
|
||||
|
||||
import sys
|
||||
if sys.version_info < (3,):
|
||||
def b(x):
|
||||
return x
|
||||
else:
|
||||
import codecs
|
||||
def b(x):
|
||||
return codecs.latin_1_encode(x)[0]
|
||||
|
||||
|
||||
def prepare_response(request):
|
||||
# we need to mocked response to look a little more real
|
||||
@@ -15,7 +25,7 @@ class ClientBaseTestCase(unittest.TestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.host = 'testhost'
|
||||
self.host = 'http://testhost'
|
||||
self.api_username = 'testuser'
|
||||
self.api_key = 'testkey'
|
||||
|
||||
@@ -32,7 +42,35 @@ class ClientBaseTestCase(unittest.TestCase):
|
||||
kwargs = kwargs['params']
|
||||
self.assertEqual(kwargs.pop('api_username'), self.api_username)
|
||||
self.assertEqual(kwargs.pop('api_key'), self.api_key)
|
||||
self.assertEqual(kwargs, params)
|
||||
|
||||
if verb == 'GET':
|
||||
self.assertEqual(kwargs, params)
|
||||
|
||||
|
||||
|
||||
class TestClientRequests(ClientBaseTestCase):
|
||||
"""
|
||||
Tests for common request handling
|
||||
"""
|
||||
|
||||
@mock.patch('pydiscourse.client.requests')
|
||||
def test_empty_content_http_ok(self, mocked_requests):
|
||||
"""Empty content should not raise error
|
||||
|
||||
Critical to test against *bytestrings* rather than unicode
|
||||
"""
|
||||
mocked_response = mock.MagicMock()
|
||||
mocked_response.content = b(' ')
|
||||
mocked_response.status_code = 200
|
||||
mocked_response.headers = {"content-type": "text/plain; charset=utf-8"}
|
||||
|
||||
assert "content-type" in mocked_response.headers
|
||||
|
||||
mocked_requests.request = mock.MagicMock()
|
||||
mocked_requests.request.return_value = mocked_response
|
||||
|
||||
resp = self.client._request('GET', '/users/admin/1/unsuspend', {})
|
||||
self.assertIsNone(resp)
|
||||
|
||||
|
||||
@mock.patch('requests.request')
|
||||
@@ -84,6 +122,11 @@ class TestUser(ClientBaseTestCase):
|
||||
self.client.unsuspend(123)
|
||||
self.assertRequestCalled(request, 'PUT', '/admin/users/123/unsuspend')
|
||||
|
||||
def test_user_bagdes(self, request):
|
||||
prepare_response(request)
|
||||
self.client.user_badges('username')
|
||||
self.assertRequestCalled(request, 'GET', '/user-badges/{}.json'.format('username'))
|
||||
|
||||
|
||||
@mock.patch('requests.request')
|
||||
class TestTopics(ClientBaseTestCase):
|
||||
@@ -139,3 +182,13 @@ class MiscellaneousTests(ClientBaseTestCase):
|
||||
prepare_response(request)
|
||||
self.client.users()
|
||||
self.assertRequestCalled(request, 'GET', '/admin/users/list/active.json')
|
||||
|
||||
def test_badges(self, request):
|
||||
prepare_response(request)
|
||||
self.client.badges()
|
||||
self.assertRequestCalled(request, 'GET', '/admin/badges.json')
|
||||
|
||||
def test_grant_badge_to(self, request):
|
||||
prepare_response(request)
|
||||
self.client.grant_badge_to('username', 1)
|
||||
self.assertRequestCalled(request, 'POST', '/user_badges', username='username', badge_id=1)
|
||||
|
||||
@@ -5,3 +5,17 @@ envlist = py27, py34, py35, pypy, pypy3
|
||||
setenv =
|
||||
PYTHONPATH = {toxinidir}:{toxinidir}/pydiscourse
|
||||
commands = python setup.py test
|
||||
|
||||
[testenv:flake8]
|
||||
basepython=python
|
||||
deps=
|
||||
flake8
|
||||
flake8_docstrings
|
||||
commands=
|
||||
flake8 pydiscourse
|
||||
|
||||
[flake8]
|
||||
ignore = E126,E128
|
||||
max-line-length = 99
|
||||
exclude = .ropeproject
|
||||
max-complexity = 10
|
||||
|
||||
Reference in New Issue
Block a user