def test_blogposts_get_all(self): """Test blogpost GET all blogposts""" user = self.create_users()[1] project = ProjectFactory.create(owner=user) blogpost_1 = BlogpostFactory.create(owner=user, project=project, title='titleone', published=True) blogpost_2 = BlogpostFactory.create(owner=user, project=project, title='titletwo', published=True) blogpost_3 = BlogpostFactory.create(owner=user, project=project, title='titlethree', published=False) url = "/project/%s/blog" % project.short_name # As anonymous res = self.app.get(url, follow_redirects=True) assert res.status_code == 200, res.status_code assert 'titleone' in res.data assert 'titletwo' in res.data # As authenticated self.register() res = self.app.get(url, follow_redirects=True) assert res.status_code == 200, res.status_code assert 'titleone' in res.data assert 'titletwo' in res.data assert 'titlethree' not in res.data
def test_blogposts_get_all(self, redirect): """Test blogpost GET all blogposts""" redirect.return_value = False user = self.create_users()[1] project = ProjectFactory.create(owner=user) blogpost_1 = BlogpostFactory.create(owner=user, project=project, title='titleone', published=True) blogpost_2 = BlogpostFactory.create(owner=user, project=project, title='titletwo', published=True) blogpost_3 = BlogpostFactory.create(owner=user, project=project, title='titlethree', published=False) self.set_proj_passwd_cookie(project) url = "/project/%s/blog" % project.short_name # As anonymous res = self.app.get(url, follow_redirects=True) print res.data assert res.status_code == 200, res.status_code assert 'titleone' in res.data assert 'titletwo' in res.data # As authenticated self.register() res = self.app.get(url, follow_redirects=True) assert res.status_code == 200, res.status_code assert 'titleone' in res.data assert 'titletwo' in res.data assert 'titlethree' not in res.data
def test_delete_blogpost(self): """Test API Blogpost delete post (DEL).""" admin = UserFactory.create() owner = UserFactory.create() user = UserFactory.create() project = ProjectFactory.create(owner=owner) blogpost = BlogpostFactory.create(project=project) blogpost2 = BlogpostFactory.create(project=project) # As anon url = '/api/blogpost/%s' % blogpost.id res = self.app.delete(url) data = json.loads(res.data) assert res.status_code == 401, res.status_code # As user url = '/api/blogpost/%s?api_key=%s' % (blogpost.id, user.api_key) res = self.app.delete(url) assert res.status_code == 403, res.status_code # As owner url = '/api/blogpost/%s?api_key=%s' % (blogpost.id, owner.api_key) res = self.app.delete(url) assert res.status_code == 204, res.status_code # As admin url = '/api/blogpost/%s?api_key=%s' % (blogpost2.id, admin.api_key) res = self.app.delete(url) assert res.status_code == 204, res.status_code
def test_blogposts_get_all(self): """Test blogpost GET all blogposts""" user = self.create_users()[1] project = ProjectFactory.create(owner=user) blogpost_1 = BlogpostFactory.create(owner=user, project=project, title='titleone') blogpost_2 = BlogpostFactory.create(owner=user, project=project, title='titletwo') url = "/project/%s/blog" % project.short_name # As anonymous res = self.app.get(url, follow_redirects=True) assert res.status_code == 200, res.status_code assert 'titleone' in res.data assert 'titletwo' in res.data # As authenticated self.register() res = self.app.get(url, follow_redirects=True) assert res.status_code == 200, res.status_code assert 'titleone' in res.data assert 'titletwo' in res.data
def test_delete_blogpost(self, mock_delete): """Test API Blogpost delete post (DEL).""" mock_delete.return_value = True admin = UserFactory.create() owner = UserFactory.create() user = UserFactory.create() project = ProjectFactory.create(owner=owner) blogpost = BlogpostFactory.create(project=project) blogpost2 = BlogpostFactory.create(project=project) # As anon url = '/api/blogpost/%s' % blogpost.id res = self.app.delete(url) data = json.loads(res.data) assert res.status_code == 401, res.status_code # As user url = '/api/blogpost/%s?api_key=%s' % (blogpost.id, user.api_key) res = self.app.delete(url) assert res.status_code == 403, res.status_code # As owner url = '/api/blogpost/%s?api_key=%s' % (blogpost.id, owner.api_key) res = self.app.delete(url) assert res.status_code == 204, res.status_code assert mock_delete.called_with(blogpost.info['file_name'], blogpost.info['container']) # As admin url = '/api/blogpost/%s?api_key=%s' % (blogpost2.id, admin.api_key) res = self.app.delete(url) assert res.status_code == 204, res.status_code
def test_get_by_returns_none_if_no_blogpost(self): """Test get_by returns None if no blogpost matches the query""" BlogpostFactory.create(title='My blog', body='myblogpost') blogpost = self.blog_repo.get_by(title='notitle') assert blogpost is None, blogpost
def test_json_blogposts_get_all(self, redirect): """Test JSON blogpost GET all blogposts""" redirect.return_value = False user = self.create_users()[1] project = ProjectFactory.create(owner=user) blogpost_1 = BlogpostFactory.create(owner=user, project=project, title='titleone', published=True) blogpost_2 = BlogpostFactory.create(owner=user, project=project, title='titletwo', published=True) blogpost_3 = BlogpostFactory.create(owner=user, project=project, title='titlethree', published=False) url = "/project/%s/blog" % project.short_name # As anonymous self.set_proj_passwd_cookie(project) res = self.app_get_json(url) assert res.status_code == 200, res.status_code data = json.loads(res.data) assert 'api_key' not in data['owner'].keys() assert 'email_addr' not in data['owner'].keys() assert 'google_user_id' not in data['owner'].keys() assert 'facebook_user_id' not in data['owner'].keys() assert 'twitter_user_id' not in data['owner'].keys() assert len(data['blogposts']) == 2 for blogpost in data['blogposts']: assert blogpost['title'] in ['titleone', 'titletwo'] # As authenticated self.register() res = self.app_get_json(url, follow_redirects=True) assert res.status_code == 200, res.status_code data = json.loads(res.data) assert 'api_key' not in data['owner'].keys() assert 'email_addr' not in data['owner'].keys() assert 'google_user_id' not in data['owner'].keys() assert 'facebook_user_id' not in data['owner'].keys() assert 'twitter_user_id' not in data['owner'].keys() assert len(data['blogposts']) == 2 for blogpost in data['blogposts']: assert blogpost['title'] in ['titleone', 'titletwo'] self.signout() # As owner self.signin(email=user.email_addr, password=self.password) res = self.app_get_json(url, follow_redirects=True) assert res.status_code == 200, res.status_code data = json.loads(res.data) assert 'api_key' in data['owner'].keys() assert 'email_addr' in data['owner'].keys() assert 'google_user_id' in data['owner'].keys() assert 'facebook_user_id' in data['owner'].keys() assert 'twitter_user_id' in data['owner'].keys() assert len(data['blogposts']) == 3 for blogpost in data['blogposts']: assert blogpost['title'] in ['titleone', 'titletwo', 'titlethree'] self.signout()
def test_filter_by_no_matches(self): """Test filter_by returns an empty list if no blogposts match the query""" BlogpostFactory.create(title='My blog', body='myblogpost') retrieved_blogposts = self.blog_repo.filter_by(title='no title') assert isinstance(retrieved_blogposts, list) assert len(retrieved_blogposts) == 0, retrieved_blogposts
def test_json_blogposts_get_all(self): """Test JSON blogpost GET all blogposts""" user = self.create_users()[1] project = ProjectFactory.create(owner=user) blogpost_1 = BlogpostFactory.create(owner=user, project=project, title='titleone', published=True) blogpost_2 = BlogpostFactory.create(owner=user, project=project, title='titletwo', published=True) blogpost_3 = BlogpostFactory.create(owner=user, project=project, title='titlethree', published=False) url = "/project/%s/blog" % project.short_name # As anonymous res = self.app_get_json(url) assert res.status_code == 200, res.status_code data = json.loads(res.data) assert 'api_key' not in data['owner'].keys() assert 'email_addr' not in data['owner'].keys() assert 'google_user_id' not in data['owner'].keys() assert 'facebook_user_id' not in data['owner'].keys() assert 'twitter_user_id' not in data['owner'].keys() assert len(data['blogposts']) == 2 for blogpost in data['blogposts']: assert blogpost['title'] in ['titleone', 'titletwo'] # As authenticated self.register() res = self.app_get_json(url, follow_redirects=True) assert res.status_code == 200, res.status_code data = json.loads(res.data) assert 'api_key' not in data['owner'].keys() assert 'email_addr' not in data['owner'].keys() assert 'google_user_id' not in data['owner'].keys() assert 'facebook_user_id' not in data['owner'].keys() assert 'twitter_user_id' not in data['owner'].keys() assert len(data['blogposts']) == 2 for blogpost in data['blogposts']: assert blogpost['title'] in ['titleone', 'titletwo'] self.signout() # As owner self.signin(email=user.email_addr, password=self.password) res = self.app_get_json(url, follow_redirects=True) assert res.status_code == 200, res.status_code data = json.loads(res.data) assert 'api_key' in data['owner'].keys() assert 'email_addr' in data['owner'].keys() assert 'google_user_id' in data['owner'].keys() assert 'facebook_user_id' in data['owner'].keys() assert 'twitter_user_id' in data['owner'].keys() assert len(data['blogposts']) == 3 for blogpost in data['blogposts']: assert blogpost['title'] in ['titleone', 'titletwo', 'titlethree'] self.signout()
def test_filter_by_multiple_conditions(self): """Test filter_by supports multiple-condition queries""" BlogpostFactory.create(title='my blogpost', body='body') blogpost = BlogpostFactory.create(title='my blogpost', body='other body') retrieved_blogposts = self.blog_repo.filter_by(title='my blogpost', body='other body') assert len(retrieved_blogposts) == 1, retrieved_blogposts assert blogpost in retrieved_blogposts, retrieved_blogposts
def test_blogposts_get_all_with_hidden_app(self): """Test blogpost GET does not show hidden projects""" self.register() admin = db.session.query(User).get(1) self.signout() self.register(name='user', email='*****@*****.**') user = db.session.query(User).get(2) app = AppFactory.create(owner=user, hidden=1) blogpost = BlogpostFactory.create(owner=user, app=app, title='title') url = "/app/%s/blog" % app.short_name # As app owner res = self.app.get(url, follow_redirects=True) assert res.status_code == 200, res.status_code assert 'title' in res.data # As authenticated self.signout() self.register(name='notowner', email='*****@*****.**') res = self.app.get(url, follow_redirects=True) assert res.status_code == 403, res.status_code # As anonymous self.signout() res = self.app.get(url, follow_redirects=True) assert res.status_code == 401, res.status_code # As admin self.signin() res = self.app.get(url, follow_redirects=True) assert res.status_code == 200, res.status_code assert 'title' in res.data
def test_anonymous_user_read_given_blogpost_hidden_app(self): """Test anonymous users cannot read a given blogpost of a hidden project""" app = AppFactory.create(hidden=1) blogpost = BlogpostFactory.create(app=app) assert_raises(Unauthorized, getattr(require, 'blogpost').read, blogpost)
def test_blogpost_get_one_draft(self): """Test blogpost GET draft with id shows one blogpost""" user = self.create_users()[1] project = ProjectFactory.create(owner=user) blogpost = BlogpostFactory.create(project=project, title='title', published=False) url = "/project/%s/%s" % (project.short_name, blogpost.id) # As anonymous res = self.app.get(url, follow_redirects=True) assert res.status_code == 401, res.status_code # As authenticated self.register() self.signin() res = self.app.get(url, follow_redirects=True) assert res.status_code == 404, res.status_code # As owner url = "/project/%s/%s?api_key=%s" % (project.short_name, blogpost.id, user.api_key) res = self.app.get(url, follow_redirects=True) assert res.status_code == 200, res.status_code assert 'title' in res.data
def test_anonymous_user_read_given_blogpost(self): """Test anonymous users can read a given blogpost""" app = AppFactory.create() blogpost = BlogpostFactory.create(app=app) assert_not_raises(Exception, getattr(require, 'blogpost').read, blogpost)
def test_anonymous_user_delete_blogpost(self): """Test anonymous users cannot delete blogposts""" with self.flask_app.test_request_context('/'): blogpost = BlogpostFactory.create() assert_raises(Unauthorized, getattr(require, 'blogpost').delete, blogpost)
def test_anonymous_user_read_given_blogpost_hidden_project(self): """Test anonymous users cannot read a given blogpost of a hidden project""" project = ProjectFactory.create(hidden=1) blogpost = BlogpostFactory.create(project=project) assert_raises(Unauthorized, ensure_authorized_to, 'read', blogpost)
def test_anonymous_user_read_given_blogpost(self): """Test anonymous users can read a given blogpost""" project = ProjectFactory.create(published=True) blogpost = BlogpostFactory.create(project=project) assert_not_raises(Exception, ensure_authorized_to, 'read', blogpost)
def test_anonymous_user_read_given_blogpost_draft_project(self): """Test anonymous users cannot read a given blogpost of a draft project""" project = ProjectFactory.create(published=False) blogpost = BlogpostFactory.create(project=project) assert_raises(Unauthorized, ensure_authorized_to, 'read', blogpost)
def test_blogpost_get_one_with_hidden_app(self): """Test blogpost GET a given post id with hidden project does not show the post""" self.register() admin = user_repo.get(1) self.signout() self.register(name='user', email='*****@*****.**') user = user_repo.get(2) app = AppFactory.create(owner=user, hidden=1) blogpost = BlogpostFactory.create(app=app, title='title') url = "/app/%s/%s" % (app.short_name, blogpost.id) # As app owner res = self.app.get(url, follow_redirects=True) assert res.status_code == 200, res.status_code assert 'title' in res.data # As authenticated self.signout() self.register(name='notowner', email='*****@*****.**') res = self.app.get(url, follow_redirects=True) assert res.status_code == 403, res.status_code # As anonymous self.signout() res = self.app.get(url, follow_redirects=True) assert res.status_code == 401, res.status_code # As admin self.signin() res = self.app.get(url, follow_redirects=True) assert res.status_code == 200, res.status_code assert 'title' in res.data
def test_blogposts_get_all_with_hidden_project(self): """Test blogpost GET does not show hidden projects""" self.register() admin = user_repo.get(1) self.signout() self.register(name='user', email='*****@*****.**') user = user_repo.get(2) project = ProjectFactory.create(owner=user, hidden=1) blogpost = BlogpostFactory.create(project=project, title='title') url = "/project/%s/blog" % project.short_name # As project owner res = self.app.get(url, follow_redirects=True) assert res.status_code == 200, res.status_code assert 'title' in res.data # As authenticated self.signout() self.register(name='notowner', email='*****@*****.**') res = self.app.get(url, follow_redirects=True) assert res.status_code == 403, res.status_code # As anonymous self.signout() res = self.app.get(url, follow_redirects=True) assert res.status_code == 401, res.status_code # As admin self.signin() res = self.app.get(url, follow_redirects=True) assert res.status_code == 200, res.status_code assert 'title' in res.data
def test_blogpost_update_by_non_owner(self): """Test blogpost update by non owner of the project is forbidden""" self.register() user = user_repo.get(1) project = ProjectFactory.create(owner=user) blogpost = BlogpostFactory.create(project=project, title='title', body='body') url = "/project/%s/new-blogpost" % project.short_name self.signout() self.register(name='notowner', email='*****@*****.**') url = "/project/%s/%s/update" % (project.short_name, blogpost.id) res = self.app.get(url, follow_redirects=True) assert res.status_code == 403, res.status_code res = self.app.post(url, data={ 'title': 'new title', 'body': 'body' }, follow_redirects=True) assert res.status_code == 403, res.status_code blogpost = blog_repo.get_by() assert blogpost.title == 'title', blogpost.title
def test_blogpost_update_errors(self): """Test blogposts update for non existing projects raises errors""" self.register() self.signin() user = user_repo.get(1) project1 = ProjectFactory.create(owner=user) project2 = ProjectFactory.create(owner=user) blogpost = BlogpostFactory.create(project=project1, body='body') # To a non-existing project url = "/project/non-existing-project/%s/update" % blogpost.id res = self.app.post(url, data={'title':'new title', 'body':'body'}, follow_redirects=True) assert res.status_code == 404, res.status_code # To a non-existing post url = "/project/%s/999999/update" % project1.short_name res = self.app.post(url, data={'title':'new title', 'body':'body'}, follow_redirects=True) assert res.status_code == 404, res.status_code # To an existing post but with a project in the URL it does not belong to url = "/project/%s/%s/update" % (project2.short_name, blogpost.id) res = self.app.post(url, data={'title':'new title', 'body':'body'}, follow_redirects=True) assert res.status_code == 404, res.status_code
def test_endpoints_with_password_protection(self): """Test all the endpoints for "reading" a project use password protection """ endpoints_requiring_password = ( '/', '/tutorial', '/1/results.json', '/tasks/', '/tasks/browse', '/stats', '/blog', '/1', '/task/1') project = ProjectFactory.create() TaskFactory.create(project=project) BlogpostFactory.create(project=project, published=True) project.set_password('mysecret') project_repo.update(project) for endpoint in endpoints_requiring_password: res = self.app.get('/project/%s%s' % (project.short_name, endpoint), follow_redirects=True) assert 'Enter the password to contribute' in res.data, endpoint
def test_blogpost_update_errors(self): """Test blogposts update for non existing projects raises errors""" self.register() user = user_repo.get(1) project1 = ProjectFactory.create(owner=user) project2 = ProjectFactory.create(owner=user) blogpost = BlogpostFactory.create(project=project1, body='body') # To a non-existing project url = "/project/non-existing-project/%s/update" % blogpost.id res = self.app.post(url, data={'title':'new title', 'body':'body'}, follow_redirects=True) assert res.status_code == 404, res.status_code # To a non-existing post url = "/project/%s/999999/update" % project1.short_name res = self.app.post(url, data={'title':'new title', 'body':'body'}, follow_redirects=True) assert res.status_code == 404, res.status_code # To an existing post but with a project in the URL it does not belong to url = "/project/%s/%s/update" % (project2.short_name, blogpost.id) res = self.app.post(url, data={'title':'new title', 'body':'body'}, follow_redirects=True) assert res.status_code == 404, res.status_code
def test_blogpost_update_errors(self): """Test blogposts update for non existing apps raises errors""" self.register() user = db.session.query(User).get(1) app1 = AppFactory.create(owner=user) app2 = AppFactory.create(owner=user) blogpost = BlogpostFactory.create(owner=user, app=app1, body='body') # To a non-existing app url = "/app/non-existing-app/%s/update" % blogpost.id res = self.app.post(url, data={'title':'new title', 'body':'body'}, follow_redirects=True) assert res.status_code == 404, res.status_code # To a non-existing post url = "/app/%s/999999/update" % app1.short_name res = self.app.post(url, data={'title':'new title', 'body':'body'}, follow_redirects=True) assert res.status_code == 404, res.status_code # To an existing post but with a project in the URL it does not belong to url = "/app/%s/%s/update" % (app2.short_name, blogpost.id) res = self.app.post(url, data={'title':'new title', 'body':'body'}, follow_redirects=True) assert res.status_code == 404, res.status_code
def test_blogpost_update_by_owner(self, mock_redirect): """Test blogposts, project owners can update""" self.register() self.signin() user = user_repo.get(1) project = ProjectFactory.create(owner=user) blogpost = BlogpostFactory.create(project=project) url = "/project/%s/%s/update" % (project.short_name, blogpost.id) res = self.app.get(url, follow_redirects=True) assert res.status_code == 200, res.status_code res = self.app.post(url, data={'id': blogpost.id, 'title':'blogpost title', 'body':'new body', 'published': True}, follow_redirects=True) assert res.status_code == 200, res.status_code mock_redirect.assert_called_with('/project/%E2%9C%93project1/blog') blogpost = blog_repo.get_by(title='blogpost title') assert blogpost.title == 'blogpost title', blogpost.title assert blogpost.body == 'new body', blogpost.body assert blogpost.published, blogpost.published
def test_endpoints_with_password_protection(self): """Test all the endpoints for "reading" a project use password protection """ endpoints_requiring_password = ( '/', '/tutorial', '/1/results.json', '/tasks/', '/tasks/browse', '/tasks/export', '/stats', '/blog', '/1', '/task/1') project = ProjectFactory.create() TaskFactory.create(project=project) BlogpostFactory.create(project=project) project.set_password('mysecret') project_repo.update(project) for endpoint in endpoints_requiring_password: res = self.app.get('/project/%s%s' % (project.short_name, endpoint), follow_redirects=True) assert 'Enter the password to contribute' in res.data, endpoint
def test_anonymous_user_delete_blogpost(self): """Test anonymous users cannot delete blogposts""" blogpost = BlogpostFactory.create() assert_raises(Unauthorized, getattr(require, 'blogpost').delete, blogpost)
def test_admin_authenticated_user_delete_blogpost(self): """Test authenticated user can delete any blogpost if is admin""" admin = UserFactory.create() blogpost = BlogpostFactory.create() assert self.mock_admin.id != blogpost.owner.id assert_not_raises(Exception, ensure_authorized_to, 'delete', blogpost)
def test_get_returns_blogpost(self): """Test get method returns a blogpost if exists""" blogpost = BlogpostFactory.create() retrieved_blogpost = self.blog_repo.get(blogpost.id) assert blogpost == retrieved_blogpost, retrieved_blogpost
def test_get_by(self): """Test get_by returns a blogpost with the specified attribute""" blogpost = BlogpostFactory.create(title='My blog', body='myblogpost') retrieved_blogpost = self.blog_repo.get_by(title=blogpost.title) assert blogpost == retrieved_blogpost, retrieved_blogpost
def test_update_fails_if_integrity_error(self): """Test update raises a DBIntegrityError if the instance to be updated lacks a required value""" blogpost = BlogpostFactory.create() blogpost.title = None assert_raises(DBIntegrityError, self.blog_repo.update, blogpost)
def test_admin_authenticated_user_delete_blogpost(self): """Test authenticated user can delete any blogpost if is admin""" admin = UserFactory.create() blogpost = BlogpostFactory.create() assert self.mock_admin.id != blogpost.owner.id assert_not_raises(Exception, getattr(require, 'blogpost').delete, blogpost)
def test_delete(self): """Test delete removes the blogpost instance""" blogpost = BlogpostFactory.create() self.blog_repo.delete(blogpost) deleted = self.blog_repo.get(blogpost.id) assert deleted is None, deleted
def test_owner_delete_blogpost(self): """Test authenticated user can delete a blogpost if is the post owner""" owner = UserFactory.create_batch(2)[1] app = AppFactory.create() blogpost = BlogpostFactory.create(app=app, owner=owner) assert self.mock_authenticated.id == blogpost.owner.id assert_not_raises(Exception, getattr(require, 'blogpost').delete, blogpost)
def test_non_owner_authenticated_user_delete_blogpost(self): """Test authenticated user cannot delete a blogpost if is not the post owner and is not admin""" blogpost = BlogpostFactory.create() assert self.mock_authenticated.id != blogpost.owner.id assert not self.mock_authenticated.admin assert_raises(Forbidden, ensure_authorized_to, 'delete', blogpost)
def test_owner_delete_blogpost(self): """Test authenticated user can delete a blogpost if is the post owner""" owner = UserFactory.create_batch(2)[1] project = ProjectFactory.create() blogpost = BlogpostFactory.create(project=project, owner=owner) assert self.mock_authenticated.id == blogpost.owner.id assert_not_raises(Exception, ensure_authorized_to, 'delete', blogpost)
def test_admin_read_given_blogpost_draft_project(self): """Test admin can read a given blogpost of a draft project""" admin = UserFactory.create() project = ProjectFactory.create(published=False) blogpost = BlogpostFactory.create(project=project) assert self.mock_admin.id != project.owner.id assert_not_raises(Exception, ensure_authorized_to, 'read', blogpost)
def test_non_owner_authenticated_user_delete_blogpost(self): """Test authenticated user cannot delete a blogpost if is not the post owner and is not admin""" blogpost = BlogpostFactory.create() assert self.mock_authenticated.id != blogpost.owner.id assert not self.mock_authenticated.admin assert_raises(Forbidden, getattr(require, 'blogpost').delete, blogpost)
def test_admin_read_given_blogpost_hidden_app(self): """Test admin can read a given blogpost of a hidden project""" admin = UserFactory.create() app = AppFactory.create(hidden=1) blogpost = BlogpostFactory.create(app=app) assert self.mock_admin.id != app.owner.id assert_not_raises(Exception, getattr(require, 'blogpost').read, blogpost)
def test_owner_read_given_blogpost(self): """Test authenticated user can read a given blogpost if is the project owner""" owner = UserFactory.create_batch(2)[1] app = AppFactory.create(owner=owner) blogpost = BlogpostFactory.create(app=app) assert self.mock_authenticated.id == app.owner.id assert_not_raises(Exception, getattr(require, 'blogpost').read, blogpost)