def test_thread_cache(app, authed_client): thread = ForumThread.from_pk(1) cache.cache_model(thread, timeout=60) thread = ForumThread.from_pk(1) assert thread.id == 1 assert thread.topic == 'New Site' assert cache.ttl(thread.cache_key) < 61
def test_thread_can_access_explicit_disallow(app, authed_client): db.engine.execute( "DELETE FROM users_permissions WHERE permission LIKE 'forumaccess%%'") add_permissions(app, 'forumaccess_forum_1') db.session.execute( """INSERT INTO users_permissions (user_id, permission, granted) VALUES (1, 'forumaccess_thread_1', 'f')""") with pytest.raises(_403Exception): ForumThread.from_pk(1, error=True)
def test_delete_forum(app, authed_client): add_permissions(app, 'forums_view', 'forums_forums_modify') sub_thread = ForumThread.from_pk( 5) # Cache - thread isn't deleted, belongs to category response = authed_client.delete('/forums/5') check_json_response(response, 'Forum 5 (Yacht Funding) has been deleted.') forum = ForumThread.from_pk(5, include_dead=True) assert forum.deleted sub_thread = ForumThread.from_pk(5, include_dead=True) assert sub_thread.deleted
def test_thread_get_from_forum_cached(app, authed_client): cache.set(ForumThread.__cache_key_of_forum__.format(id=2), [1, 5], timeout=60) ForumThread.from_pk(1) ForumThread.from_pk(5) # noqa cache these threads = ForumThread.from_forum(2, page=1, limit=50) assert len(threads) == 2 for thread in threads: if thread.topic == 'Donations?' and thread.id == 5: break else: raise AssertionError('A real thread not called')
def test_thread_last_viewed_post_deleted(app, authed_client): thread = ForumThread.from_pk(5) last_post = thread.last_viewed_post assert last_post.id == 3 assert last_post.thread_id == 5 assert 3 == cache.get( ForumLastViewedPost.__cache_key__.format(thread_id=5, user_id=1))
def test_thread_last_post_already_cached(app, authed_client): thread = ForumThread.from_pk(2, include_dead=True) post = ForumPost.from_pk(6) cache.cache_model(post, timeout=60) post = thread.last_post assert post.contents == 'Delete this' assert cache.ttl(post.cache_key) < 61
def test_serialize_very_detailed(app, authed_client): add_permissions(app, 'forums_threads_modify', 'forums_threads_modify_advanced') thread = ForumThread.from_pk(3) data = NewJSONEncoder().default(thread) check_dictionary( data, { 'id': 3, 'topic': 'Using PHP', 'locked': True, 'sticky': True, 'deleted': False, 'post_count': 1, 'subscribed': True, }, ) assert 'forum' in data and data['forum']['id'] == 2 assert 'creator' in data and data['creator']['id'] == 2 assert 'last_post' in data and data['last_post']['id'] == 2 assert 'last_viewed_post' in data and data['last_viewed_post']['id'] == 2 assert ('posts' in data and len(data['posts']) == 1 and data['posts'][0]['id'] == 2) assert 'created_time' in data and isinstance(data['created_time'], int) assert 'poll' in data and data['poll']['id'] == 3 assert 'thread_notes' in data assert ('thread_notes' in data and len(data['thread_notes']) == 1 and data['thread_notes'][0]['id'] == 3)
def test_thread_can_access_implicit_forum(app, authed_client): db.engine.execute( "DELETE FROM users_permissions WHERE permission LIKE 'forumaccess%%'") add_permissions(app, 'forumaccess_forum_1') thread = ForumThread.from_pk(1) assert thread.id == 1 assert thread.topic == 'New Site'
def modify_post(id: int, sticky: bool = None, contents: str = None) -> flask.Response: """ This is the endpoint for forum post editing. The ``forums_posts_modify`` permission is required to access this endpoint. Posts can be marked sticky with this endpoint. .. :quickref: ForumThread; Edit a forum post. **Example request**: .. parsed-literal:: PUT /forums/posts/6 HTTP/1.1 { "sticky": true } **Example response**: .. parsed-literal:: { "status": "success", "response": "<ForumPost>" } :>json dict response: The modified forum post :statuscode 200: Modification successful :statuscode 400: Modification unsuccessful :statuscode 404: Forum post does not exist """ post = ForumPost.from_pk(id, _404=True) assert_user(post.user_id, 'forums_posts_modify') thread = ForumThread.from_pk(post.thread_id) if not thread: raise APIException(f'ForumPost {id} does not exist.') if thread.locked and not flask.g.user.has_permission( 'forums_posts_modify'): raise APIException('You cannot modify posts in a locked thread.') if contents is not None: ForumPostEditHistory.new( post_id=post.id, editor_id=post.edited_user_id or post.user_id, contents=post.contents, time=datetime.utcnow().replace(tzinfo=pytz.utc), ) post.contents = contents post.edited_user_id = flask.g.user.id post.edited_time = datetime.utcnow().replace(tzinfo=pytz.utc) if flask.g.user.has_permission('forums_posts_modify'): if sticky is not None: post.sticky = sticky db.session.commit() return flask.jsonify(post)
def test_thread_last_viewed_post_cached(app, authed_client): cache.set(ForumLastViewedPost.__cache_key__.format(thread_id=1, user_id=1), 2) thread = ForumThread.from_pk(1) last_post = thread.last_viewed_post assert last_post.id == 2 assert last_post.thread_id == 3 assert 2 == cache.get( ForumLastViewedPost.__cache_key__.format(thread_id=1, user_id=1))
def alter_thread_subscription(thread_id: int) -> flask.Response: """ This is the endpoint for forum thread subscription. The ``forums_subscriptions_modify`` permission is required to access this endpoint. A POST request creates a subscription, whereas a DELETE request removes a subscription. .. :quickref: ForumThreadSubscription; Subscribe to a forum thread. **Example response**: .. parsed-literal:: { "status": "success", "response": "Successfully subscribed to thread 2." } :>json str response: Success or failure message :statuscode 200: Subscription alteration successful :statuscode 400: Subscription alteration unsuccessful :statuscode 404: Forum thread does not exist """ thread = ForumThread.from_pk(thread_id, _404=True) subscription = ForumThreadSubscription.from_attrs( user_id=flask.g.user.id, thread_id=thread.id ) if flask.request.method == 'POST': if subscription: raise APIException( f'You are already subscribed to thread {thread_id}.' ) ForumThreadSubscription.new( user_id=flask.g.user.id, thread_id=thread_id ) return flask.jsonify(f'Successfully subscribed to thread {thread_id}.') else: # method = DELETE if not subscription: raise APIException( f'You are not subscribed to thread {thread_id}.' ) db.session.delete(subscription) db.session.commit() cache.delete( ForumThreadSubscription.__cache_key_users__.format( thread_id=thread_id ) ) cache.delete( ForumThreadSubscription.__cache_key_of_user__.format( user_id=flask.g.user.id ) ) return flask.jsonify( f'Successfully unsubscribed from thread {thread_id}.' )
def test_edit_thread_skips(app, authed_client): add_permissions(app, 'forums_view', 'forums_threads_modify') response = authed_client.put( '/forums/threads/1', data=json.dumps({'sticky': True}) ) check_json_response( response, {'id': 1, 'topic': 'New Site', 'locked': False, 'sticky': True}, ) thread = ForumThread.from_pk(1) assert thread.sticky is True
def create_post(contents: str, thread_id: int) -> flask.Response: """ This is the endpoint for forum posting. The ``forums_posts_modify`` permission is required to access this endpoint. .. :quickref: ForumPost; Create a forum post. **Example request**: .. parsed-literal:: POST /forums/posts HTTP/1.1 { "topic": "How do I get easy ration?", "forum_id": 4 } **Example response**: .. parsed-literal:: { "status": "success", "response": "<ForumPost>" } :>json dict response: The newly created forum post :statuscode 200: Creation successful :statuscode 400: Creation unsuccessful """ thread = ForumThread.from_pk(thread_id, _404=True) if thread.locked and not flask.g.user.has_permission( 'forums_post_in_locked'): raise APIException('You cannot post in a locked thread.') thread_last_post = thread.last_post if (thread_last_post and thread_last_post.user_id == flask.g.user.id and not flask.g.user.has_permission('forums_posts_double')): if len(thread_last_post.contents) + len(contents) > 255997: raise APIException('Post could not be merged into previous post ' '(must be <256,000 characters combined).') return modify_post( id=thread_last_post.id, contents=f'{thread_last_post.contents}\n\n\n{contents}', skip_validation=True, ) post = ForumPost.new(thread_id=thread_id, user_id=flask.g.user.id, contents=contents) return flask.jsonify(post)
def view_thread(id: int, page: int = 1, limit: int = 50, include_dead: bool = False) -> flask.Response: """ This endpoint allows users to view details about a forum and its threads. .. :quickref: ForumThread; View a forum thread. **Example request**: .. parsed-literal:: GET /forums/threads/1 HTTP/1.1 Host: pul.sar Accept: application/json **Example response**: .. parsed-literal:: HTTP/1.1 200 OK Vary: Accept Content-Type: application/json { "status": "success", "response": [ { } ] } :>json list response: A forum thread :statuscode 200: View successful :statuscode 403: User does not have permission to view thread :statuscode 404: Thread does not exist """ thread = ForumThread.from_pk( id, _404=True, include_dead=flask.g.user.has_permission( 'forums_threads_modify_advanced'), ) thread.set_posts( page, limit, include_dead and flask.g.user.has_permission('forums_posts_modify_advanced'), ) return flask.jsonify(thread)
def test_delete_thread(app, authed_client): add_permissions(app, 'forums_view', 'forums_threads_modify_advanced') post = ForumPost.from_pk( 3 ) # Cache - post isn't deleted, belongs to thread response = authed_client.delete('/forums/threads/5') check_json_response( response, 'ForumThread 5 (Donations?) has been deleted.' ) thread = ForumThread.from_pk(5, include_dead=True) assert thread.deleted post = ForumPost.from_pk(3, include_dead=True) assert post.deleted
def test_edit_thread(app, authed_client): add_permissions(app, 'forums_view', 'forums_threads_modify') response = authed_client.put( '/forums/threads/1', data=json.dumps({'topic': 'Bite', 'forum_id': 4, 'locked': True}), ) check_json_response( response, {'id': 1, 'topic': 'Bite', 'locked': True, 'sticky': False} ) assert response.get_json()['response']['forum']['id'] == 4 thread = ForumThread.from_pk(1) assert thread.id == 1 assert thread.topic == 'Bite' assert thread.locked is True assert thread.sticky is False
def delete_thread(id: int) -> flask.Response: """ This is the endpoint for forum thread deletion . The ``forums_threads_modify_advanced`` permission is required to access this endpoint. All posts in a deleted forum will also be deleted. .. :quickref: ForumThread; Delete a forum thread. **Example request**: .. parsed-literal:: DELETE /forums/threads/2 HTTP/1.1 Host: pul.sar Accept: application/json Content-Type: application/json **Example response**: .. parsed-literal:: HTTP/1.1 200 OK Vary: Accept Content-Type: application/json { "status": "success", "response": { } } :>json dict response: The deleted forum thread :statuscode 200: Deletion successful :statuscode 400: Deletion unsuccessful :statuscode 404: Forum thread does not exist """ thread = ForumThread.from_pk(id, _404=True) thread.deleted = True ForumPost.update_many(pks=ForumPost.get_ids_from_thread(thread.id), update={'deleted': True}) return flask.jsonify( f'ForumThread {id} ({thread.topic}) has been deleted.')
def test_serialize_nested(app, authed_client): add_permissions(app, 'forums_threads_modify_advanced') thread = ForumThread.from_pk(3) data = NewJSONEncoder()._objects_to_dict(thread.serialize(nested=True)) check_dictionary( data, { 'id': 3, 'topic': 'Using PHP', 'locked': True, 'sticky': True, 'deleted': False, 'post_count': 1, 'subscribed': True, }, ) assert 'creator' in data and data['creator']['id'] == 2 assert 'last_post' in data and data['last_post']['id'] == 2 assert 'last_viewed_post' in data and data['last_viewed_post']['id'] == 2 assert 'created_time' in data and isinstance(data['created_time'], int)
def test_serialize_no_perms(app, authed_client): thread = ForumThread.from_pk(3) data = NewJSONEncoder().default(thread) check_dictionary( data, { 'id': 3, 'topic': 'Using PHP', 'locked': True, 'sticky': True, 'post_count': 1, 'subscribed': True, }, ) assert 'forum' in data and data['forum']['id'] == 2 assert 'creator' in data and data['creator']['id'] == 2 assert 'last_post' in data and data['last_post']['id'] == 2 assert 'last_viewed_post' in data and data['last_viewed_post']['id'] == 2 assert ('posts' in data and len(data['posts']) == 1 and data['posts'][0]['id'] == 2) assert 'created_time' in data and isinstance(data['created_time'], int) assert 'poll' in data and data['poll']['id'] == 3
def test_thread_last_viewed_post_none(app, authed_client): thread = ForumThread.from_pk(1) assert thread.last_viewed_post is None assert not cache.get( ForumLastViewedPost.__cache_key__.format(thread_id=1, user_id=1))
def test_thread_last_post_empty(app, authed_client): thread = ForumThread.from_pk(1, include_dead=True) assert thread.last_post is None
def test_thread_subscribed_property(app, authed_client): assert ForumThread.from_pk(5).subscribed is False
def test_thread_last_post_from_cache(app, authed_client): cache.set(ForumThread.__cache_key_last_post__.format(id=2), 2) thread = ForumThread.from_pk(2, include_dead=True) post = thread.last_post assert post.contents == 'Why the f**k is Gazelle in PHP?!'
def test_thread_last_post(app, authed_client): thread = ForumThread.from_pk(2, include_dead=True) post = thread.last_post assert post.contents == 'Delete this'
def test_thread_no_permissions(app, authed_client): db.engine.execute( "DELETE FROM users_permissions WHERE permission LIKE 'forumaccess%%'") with pytest.raises(_403Exception): ForumThread.from_pk(1, error=True)
def test_delete_thread_no_posts(app, authed_client): add_permissions(app, 'forums_view', 'forums_threads_modify_advanced') response = authed_client.delete('/forums/threads/1') check_json_response(response, 'ForumThread 1 (New Site) has been deleted.') thread = ForumThread.from_pk(1, include_dead=True) assert thread.deleted
def test_thread_from_pk(app, authed_client): thread = ForumThread.from_pk(1) assert thread.id == 1 assert thread.topic == 'New Site'
def test_thread_last_viewed_none_available(app, authed_client): db.session.execute("DELETE FROM forums_posts WHERE id > 5") thread = ForumThread.from_pk(4) assert thread.last_viewed_post is None
def modify_thread( id: int, topic: str = None, forum_id: int = None, locked: bool = None, sticky: bool = None, ) -> flask.Response: """ This is the endpoint for forum thread editing. The ``forums_threads_modify`` permission is required to access this endpoint. The topic, forum_id, locked, and sticky attributes can be changed here. .. :quickref: ForumThread; Edit a forum thread. **Example request**: .. parsed-literal:: PUT /forums/threads/6 HTTP/1.1 Host: pul.sar Accept: application/json Content-Type: application/json { "topic": "This does not contain typos.", "forum_id": 2, "locked": true, "sticky": false } **Example response**: .. parsed-literal:: HTTP/1.1 200 OK Vary: Accept Content-Type: application/json { "status": "success", "response": { } } :>json dict response: The edited forum thread :statuscode 200: Editing successful :statuscode 400: Editing unsuccessful :statuscode 404: Forum thread does not exist """ thread = ForumThread.from_pk(id, _404=True) if topic: thread.topic = topic if forum_id and Forum.is_valid(forum_id, error=True): thread.forum_id = forum_id if locked is not None: thread.locked = locked if sticky is not None: thread.sticky = sticky db.session.commit() return flask.jsonify(thread)
def test_thread_from_pk_deleted(app, authed_client): assert ForumThread.from_pk(2) is None