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
@@ -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
@@ -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
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):

29 changes: 13 additions & 16 deletions isso/views/comments.py
Original file line number Diff line number Diff line change
@@ -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.
@@ -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):
@@ -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

@@ -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
@@ -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):
@@ -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:
@@ -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