Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

views: moderation: Add unit tests, proper response instead of "Yo", apiDoc clarifications #805

Merged
merged 11 commits into from
Mar 5, 2022
Merged
6 changes: 6 additions & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,11 @@ In chronological order:
* Update Polish translation
* Redirect to comment after moderation

* fliiiix <l33t.name>
* Import disqus posts without Email
* Import disqus post without IP
* Fixing minor code inconsistencies
* Testing for moderation and unsubscribe

* [Your name or handle] <[email or website]>
* [Brief summary of your changes]
102 changes: 102 additions & 0 deletions isso/tests/test_comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,108 @@ def testAddComment(self):
self.app.db.comments.activate(1)
self.assertEqual(self.client.get('/?uri=test').status_code, 200)

def testModerateComment(self):

id_ = 1
fliiiix marked this conversation as resolved.
Show resolved Hide resolved
signed = self.app.sign(id_)

# Create new comment, should have mode=2 (pending moderation)
rv = self.client.post(
'/new?uri=/moderated', data=json.dumps({"text": "..."}))
self.assertEqual(rv.status_code, 202)
self.assertEqual(self.client.get('/id/1').status_code, 200)
self.assertEqual(self.app.db.comments.get(id_)["mode"], 2)
self.assertEqual(self.app.db.comments.get(id_)["text"], "...")

# GET should return some html form
action = "activate"
rv_activate_get = self.client.get('/id/%d/%s/%s' % (id_, action, signed))
self.assertEqual(rv_activate_get.status_code, 200)
self.assertIn(b"Activate: Are you sure?", rv_activate_get.data)
self.assertIn(b"http://invalid.local/moderated#isso-1", rv_activate_get.data)

# Activate comment
action = "activate"
rv_activated = self.client.post('/id/%d/%s/%s' % (id_, action, signed))
self.assertEqual(rv_activated.status_code, 200)
self.assertEqual(rv_activated.data, b"Comment has been activated")

# Activating should be idempotent
rv_activated = self.client.post('/id/%d/%s/%s' % (id_, action, signed))
self.assertEqual(rv_activated.status_code, 200)
self.assertEqual(rv_activated.data, b"Already activated")

# Comment should have mode=1 (activated)
self.assertEqual(self.app.db.comments.get(id_)["mode"], 1)

# Edit comment
action = "edit"
rv_edit = self.client.post('/id/%d/%s/%s' % (id_, action, signed), data=json.dumps({"text": "new text"}))
self.assertEqual(rv_edit.status_code, 200)
self.assertEqual(json.loads(rv_edit.data)["id"], id_)
self.assertEqual(self.app.db.comments.get(id_)["text"], "new text")

# Wrong action on comment is handled by the routing
action = "foo"
rv_wrong_action = self.client.post('/id/%d/%s/%s' % (id_, action, signed))
self.assertEqual(rv_wrong_action.status_code, 404)

# Delete comment
action = "delete"
rv_deleted = self.client.post('/id/%d/%s/%s' % (id_, action, signed))
self.assertEqual(rv_deleted.status_code, 200)
self.assertEqual(rv_deleted.data, b"Comment has been deleted")

# Comment should no longer exist
self.assertEqual(self.app.db.comments.get(id_), None)


class TestUnsubscribe(unittest.TestCase):

def setUp(self):
fd, self.path = tempfile.mkstemp()
conf = config.load(
pkg_resources.resource_filename('isso', 'defaults.ini'))
conf.set("general", "dbpath", self.path)
conf.set("moderation", "enabled", "true")
conf.set("guard", "enabled", "off")
conf.set("hash", "algorithm", "none")

class App(Isso, core.Mixin):
pass

self.app = App(conf)
self.app.wsgi_app = FakeIP(self.app.wsgi_app, "192.168.1.1")
self.client = JSONClient(self.app, Response)

# add default comment
rv = self.client.post(
'/new?uri=test', data=json.dumps({"text": "..."}))
self.assertEqual(rv.status_code, 202)

def tearDown(self):
os.unlink(self.path)

def testUnsubscribe(self):
id_ = 1
email = "[email protected]"
key = self.app.sign(('unsubscribe', email))

# GET should return some html form
rv_unsubscribe_get = self.client.get('/id/%d/unsubscribe/%s/%s' % (id_, email, key))
self.assertEqual(rv_unsubscribe_get.status_code, 200)
self.assertIn(b"Successfully unsubscribed", rv_unsubscribe_get.data)

# Incomplete key should fail
key = self.app.sign(['unsubscribe'])
rv_incomplete_key = self.client.get('/id/%d/unsubscribe/%s/%s' % (id_, email, key))
self.assertEqual(rv_incomplete_key.status_code, 403)

# Wrong key type should fail
key = self.app.sign(1)
rv_wrong_key_type = self.client.get('/id/%d/unsubscribe/%s/%s' % (id_, email, key))
self.assertEqual(rv_wrong_key_type.status_code, 403)


class TestPurgeComments(unittest.TestCase):

Expand Down
29 changes: 13 additions & 16 deletions isso/views/comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ def delete(self, environ, request, id, key=None):
return resp

"""
@api {get} /id/:id/:email/key unsubscribe
@api {get} /id/:id/unsubscribe/:email/key unsubscribe
@apiGroup Comment
@apiDescription
Opt out from getting any further email notifications about replies to a particular comment. In order to use this endpoint, the requestor needs a `key` that is usually obtained from an email sent out by isso.
Expand All @@ -569,17 +569,11 @@ def delete(self, environ, request, id, key=None):
@apiSuccessExample {html} Using GET:
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;script&gt;
if (confirm('Delete: Are you sure?')) {
xhr = new XMLHttpRequest;
xhr.open('POST', window.location.href);
xhr.send(null);
}
&lt;/script&gt;

@apiSuccessExample Using POST:
Yo
&lt;head&gtSuccessfully unsubscribed&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;You have been unsubscribed from replies in the given conversation.&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;
"""

def unsubscribe(self, environ, request, id, email, key):
Expand All @@ -590,6 +584,9 @@ def unsubscribe(self, environ, request, id, email, key):
except (BadSignature, SignatureExpired):
raise Forbidden

if not isinstance(rv, list) or len(rv) != 2:
raise Forbidden

if rv[0] != 'unsubscribe' or rv[1] != email:
raise Forbidden

Expand Down Expand Up @@ -624,7 +621,7 @@ def unsubscribe(self, environ, request, id, email, key):

@apiParam {number} id
The id of the comment to moderate.
@apiParam {string=activate,delete} action
@apiParam {string=activate,edit,delete} action
`activate` to publish the comment (change its mode to `1`).
`delete` to delete the comment
@apiParam {string} key
Expand All @@ -649,7 +646,7 @@ def unsubscribe(self, environ, request, id, email, key):
&lt;/script&gt;

@apiSuccessExample Using POST:
Yo
Comment has been deleted
"""

def moderate(self, environ, request, id, action, key):
Expand Down Expand Up @@ -689,7 +686,7 @@ def moderate(self, environ, request, id, action, key):
with self.isso.lock:
self.comments.activate(id)
self.signal("comments.activate", thread, item)
return Response("Yo", 200)
return Response("Comment has been activated", 200)
elif action == "edit":
data = request.get_json()
with self.isso.lock:
Expand All @@ -704,7 +701,7 @@ def moderate(self, environ, request, id, action, key):
self.cache.delete(
'hash', (item['email'] or item['remote_addr']).encode('utf-8'))
self.signal("comments.delete", id)
return Response("Yo", 200)
return Response("Comment has been deleted", 200)

"""
@api {get} / get comments
Expand Down