forked from gergely/calendar-social
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			friend-lis
			...
			drone-ci
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 86f86996f6 | 
							
								
								
									
										17
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| pipeline: | ||||
|   build: | ||||
|     image: python:3.6.6-alpine3.6 | ||||
|     commands: | ||||
|       - apk update | ||||
|       - apk add gcc libffi-dev linux-headers libc-dev | ||||
|       - pip3.6 install -U pip setuptools pipenv | ||||
|       - pipenv install | ||||
|   lint: | ||||
|     image: python:3.6.6-alpine3.6 | ||||
|     commands: | ||||
|       - apk update | ||||
|       - apk add gcc libffi-dev linux-headers libc-dev | ||||
|       - pip3.6 install -U pip setuptools pipenv | ||||
|       - pipenv install | ||||
|       - pipenv install --dev | ||||
|       - pipenv run pylint calsocial | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| __pycache__/ | ||||
| /calsocial/local.db | ||||
| /messages.pot | ||||
| /calsocial/translations/*/LC_MESSAGES/*.mo | ||||
| /app/translations/*/LC_MESSAGES/*.mo | ||||
| /.pytest_cache/ | ||||
|   | ||||
| @@ -170,7 +170,7 @@ class CalendarSocialApp(Flask): | ||||
|  | ||||
|         calendar = GregorianCalendar(timestamp.timestamp()) | ||||
|  | ||||
|         return render_template('index.html', calendar=calendar, user_only=True) | ||||
|         return render_template('index.html', calendar=calendar) | ||||
|  | ||||
|     @staticmethod | ||||
|     @route('/register', methods=['POST', 'GET']) | ||||
| @@ -217,7 +217,7 @@ class CalendarSocialApp(Flask): | ||||
|         form = EventForm() | ||||
|  | ||||
|         if form.validate_on_submit(): | ||||
|             event = Event(profile=current_user.profile) | ||||
|             event = Event(user=current_user) | ||||
|             form.populate_obj(event) | ||||
|  | ||||
|             db.session.add(event) | ||||
| @@ -255,7 +255,7 @@ class CalendarSocialApp(Flask): | ||||
|         """ | ||||
|  | ||||
|         from .forms import InviteForm | ||||
|         from .models import db, Event | ||||
|         from .models import db, Event, Invitation, Notification, NotificationAction | ||||
|  | ||||
|         try: | ||||
|             event = Event.query.filter(Event.event_uuid == event_uuid).one() | ||||
| @@ -267,7 +267,15 @@ class CalendarSocialApp(Flask): | ||||
|         form = InviteForm(event) | ||||
|  | ||||
|         if form.validate_on_submit(): | ||||
|             event.invite(current_user.profile, invitee=form.invitee.data) | ||||
|             invite = Invitation(event=event, sender=current_user.profile) | ||||
|             form.populate_obj(invite) | ||||
|             db.session.add(invite) | ||||
|  | ||||
|             notification = Notification(profile=form.invitee.data, | ||||
|                                         actor=current_user.profile, | ||||
|                                         item=event, | ||||
|                                         action=NotificationAction.invite) | ||||
|             db.session.add(notification) | ||||
|  | ||||
|             db.session.commit() | ||||
|  | ||||
| @@ -292,12 +300,11 @@ class CalendarSocialApp(Flask): | ||||
|  | ||||
|     @staticmethod | ||||
|     @route('/profile/@<string:username>/follow') | ||||
|     @login_required | ||||
|     def follow_user(username): | ||||
|         """View for following a user | ||||
|         """ | ||||
|  | ||||
|         from .models import db, Profile, User | ||||
|         from .models import db, Profile, User, UserFollow, Notification, NotificationAction | ||||
|  | ||||
|         try: | ||||
|             profile = Profile.query.join(User).filter(User.username == username).one() | ||||
| @@ -305,7 +312,16 @@ class CalendarSocialApp(Flask): | ||||
|             abort(404) | ||||
|  | ||||
|         if profile.user != current_user: | ||||
|             profile.follow(follower=current_user.profile) | ||||
|             follow = UserFollow(follower=current_user.profile, | ||||
|                                 followed=profile, | ||||
|                                 accepted_at=datetime.utcnow()) | ||||
|             db.session.add(follow) | ||||
|  | ||||
|             notification = Notification(profile=profile, | ||||
|                                         actor=current_user.profile, | ||||
|                                         item=profile, | ||||
|                                         action=NotificationAction.follow) | ||||
|             db.session.add(notification) | ||||
|  | ||||
|             db.session.commit() | ||||
|  | ||||
| @@ -383,83 +399,5 @@ class CalendarSocialApp(Flask): | ||||
|  | ||||
|         return render_template('first-steps.html', form=form) | ||||
|  | ||||
|     @staticmethod | ||||
|     @route('/edit-profile', methods=['GET', 'POST']) | ||||
|     @login_required | ||||
|     def edit_profile(): | ||||
|         """View for editing one’s profile | ||||
|         """ | ||||
|  | ||||
|         from .forms import ProfileForm | ||||
|         from .models import db | ||||
|  | ||||
|         form = ProfileForm(current_user.profile) | ||||
|  | ||||
|         if form.validate_on_submit(): | ||||
|             form.populate_obj(current_user.profile) | ||||
|             db.session.add(current_user.profile) | ||||
|             db.session.commit() | ||||
|  | ||||
|             return redirect(url_for('edit_profile')) | ||||
|  | ||||
|         return render_template('profile-edit.html', form=form) | ||||
|  | ||||
|     @staticmethod | ||||
|     @route('/all-events') | ||||
|     def all_events(): | ||||
|         """View for listing all available events | ||||
|         """ | ||||
|  | ||||
|         from .calendar_system.gregorian import GregorianCalendar | ||||
|  | ||||
|         try: | ||||
|             timestamp = datetime.fromtimestamp(float(request.args.get('date'))) | ||||
|         except TypeError: | ||||
|             timestamp = datetime.utcnow() | ||||
|  | ||||
|         calendar = GregorianCalendar(timestamp.timestamp()) | ||||
|  | ||||
|         return render_template('index.html', calendar=calendar, user_only=False) | ||||
|  | ||||
|     @staticmethod | ||||
|     @route('/follow-requests') | ||||
|     @login_required | ||||
|     def follow_requests(): | ||||
|         """View for listing follow requests | ||||
|         """ | ||||
|  | ||||
|         from .models import UserFollow | ||||
|  | ||||
|         requests = UserFollow.query \ | ||||
|                              .filter(UserFollow.followed == current_user.profile) \ | ||||
|                              .filter(UserFollow.accepted_at.is_(None)) | ||||
|  | ||||
|         return render_template('follow-requests.html', requests=requests) | ||||
|  | ||||
|     @staticmethod | ||||
|     @route('/follow-request/<int:follower_id>/accept') | ||||
|     @login_required | ||||
|     def accept_follow(follower_id): | ||||
|         """View for accepting a follow request | ||||
|         """ | ||||
|  | ||||
|         from .models import db, UserFollow | ||||
|  | ||||
|         try: | ||||
|             req = UserFollow.query \ | ||||
|                             .filter(UserFollow.followed == current_user.profile) \ | ||||
|                             .filter(UserFollow.follower_id == follower_id) \ | ||||
|                             .one() | ||||
|         except NoResultFound: | ||||
|             abort(404) | ||||
|  | ||||
|         if req.accepted_at is None: | ||||
|             req.accept() | ||||
|  | ||||
|             db.session.add(req) | ||||
|             db.session.commit() | ||||
|  | ||||
|         return redirect(url_for('follow_requests')) | ||||
|  | ||||
|  | ||||
| app = CalendarSocialApp(__name__) | ||||
|   | ||||
| @@ -330,24 +330,8 @@ class FirstStepsForm(FlaskForm): | ||||
|     display_name = StringField( | ||||
|         label=_('Display name'), | ||||
|         validators=[DataRequired()], | ||||
|         # pylint: disable=line-too-long | ||||
|         description=_('This will be shown to other users as your name.  You can use your real name, or any nickname you like.')) | ||||
|     time_zone = TimezoneField( | ||||
|         label=_('Your time zone'), | ||||
|         validators=[DataRequired()], | ||||
|         description=_('The start and end times of events will be displayed in this time zone.')) | ||||
|  | ||||
|  | ||||
| class ProfileForm(FlaskForm): | ||||
|     """Form for editing a user profile | ||||
|     """ | ||||
|  | ||||
|     display_name = StringField(label=_('Display name'), validators=[DataRequired()]) | ||||
|     locked = BooleanField(label=_('Lock profile')) | ||||
|  | ||||
|     def __init__(self, profile, *args, **kwargs): | ||||
|         kwargs.update({'display_name': profile.display_name}) | ||||
|         kwargs.update({'locked': profile.locked}) | ||||
|         FlaskForm.__init__(self, *args, **kwargs) | ||||
|  | ||||
|         self.profile = profile | ||||
|   | ||||
| @@ -246,9 +246,6 @@ class Profile(db.Model):  # pylint: disable=too-few-public-methods | ||||
|     #: The display name | ||||
|     display_name = db.Column(db.Unicode(length=80), nullable=False) | ||||
|  | ||||
|     #: If locked, a profile cannot be followed without the owner’s consent | ||||
|     locked = db.Column(db.Boolean(), default=False) | ||||
|  | ||||
|     @property | ||||
|     def fqn(self): | ||||
|         """The fully qualified name of the profile | ||||
| @@ -268,7 +265,7 @@ class Profile(db.Model):  # pylint: disable=too-few-public-methods | ||||
|             domain = '' | ||||
|         else: | ||||
|             username = self.username | ||||
|             domain = f'@{self.domain}' | ||||
|             domain = '@' + self.domain | ||||
|  | ||||
|         return f'<Profile {self.id}(@{username}{domain})>' | ||||
|  | ||||
| @@ -293,8 +290,7 @@ class Profile(db.Model):  # pylint: disable=too-few-public-methods | ||||
|  | ||||
|         return Profile.query \ | ||||
|             .join(UserFollow.followed) \ | ||||
|             .filter(UserFollow.follower == self) \ | ||||
|             .filter(UserFollow.accepted_at.isnot(None)) | ||||
|             .filter(UserFollow.follower == self) | ||||
|  | ||||
|     @property | ||||
|     def follower_list(self): | ||||
| @@ -307,23 +303,7 @@ class Profile(db.Model):  # pylint: disable=too-few-public-methods | ||||
|  | ||||
|         return Profile.query \ | ||||
|             .join(UserFollow.follower) \ | ||||
|             .filter(UserFollow.followed == self) \ | ||||
|             .filter(UserFollow.accepted_at.isnot(None)) | ||||
|  | ||||
|     @property | ||||
|     def friend_list(self): | ||||
|         """List of friends (ie. where both profiles follow each other) | ||||
|         """ | ||||
|  | ||||
|         # This will always be empty for remote profiles | ||||
|         if not self.user: | ||||
|             return [] | ||||
|  | ||||
|         reverse = db.aliased(UserFollow) | ||||
|         return UserFollow.query \ | ||||
|                          .filter(UserFollow.follower == self) \ | ||||
|                          .join(reverse, UserFollow.followed == reverse.follower) \ | ||||
|                          .filter(UserFollow.follower == reverse.followed) | ||||
|             .filter(UserFollow.followed == self) | ||||
|  | ||||
|     @property | ||||
|     def url(self): | ||||
| @@ -337,48 +317,6 @@ class Profile(db.Model):  # pylint: disable=too-few-public-methods | ||||
|  | ||||
|         return NotImplemented | ||||
|  | ||||
|     def follow(self, follower): | ||||
|         """Make ``follower`` follow this profile | ||||
|         """ | ||||
|  | ||||
|         if not isinstance(follower, Profile): | ||||
|             raise TypeError('Folloer must be a Profile object') | ||||
|  | ||||
|         timestamp = None if self.locked else datetime.utcnow() | ||||
|  | ||||
|         user_follow = UserFollow(follower=follower, followed=self, accepted_at=timestamp) | ||||
|         db.session.add(user_follow) | ||||
|         notification = self.notify(follower, self, NotificationAction.follow) | ||||
|  | ||||
|         db.session.add(notification) | ||||
|  | ||||
|         return user_follow | ||||
|  | ||||
|     def notify(self, actor, item, action): | ||||
|         """Notify this profile about ``action`` on ``item`` by ``actor`` | ||||
|  | ||||
|         :param actor: the actor who generated the notification | ||||
|         :type actor: Profile | ||||
|         :param item: the item ``action`` was performed on | ||||
|         :type item: any | ||||
|         :param action: the type of the action | ||||
|         :type action: NotificationAction, str | ||||
|         :raises TypeError: if ``actor`` is not a `Profile` object | ||||
|         :returns: the generated notification.  It is already added to the database session, but | ||||
|             not committed | ||||
|         :rtype: Notification | ||||
|         """ | ||||
|  | ||||
|         if not isinstance(actor, Profile): | ||||
|             raise TypeError('actor must be a Profile instance') | ||||
|  | ||||
|         if isinstance(action, str): | ||||
|             action = NotificationAction[action] | ||||
|  | ||||
|         notification = Notification(profile=self, actor=actor, item=item, action=action) | ||||
|  | ||||
|         return notification | ||||
|  | ||||
|  | ||||
| class Event(db.Model): | ||||
|     """Database model for events | ||||
| @@ -462,20 +400,6 @@ class Event(db.Model): | ||||
|  | ||||
|         return url_for('event_details', event_uuid=self.event_uuid) | ||||
|  | ||||
|     def invite(self, inviter, invited): | ||||
|         """Invite ``invited`` to the event | ||||
|  | ||||
|         The invitation will arrive from ``inviter``. | ||||
|         """ | ||||
|  | ||||
|         invite = Invitation(event=self, sender=inviter, invitee=invited) | ||||
|         db.session.add(invite) | ||||
|  | ||||
|         notification = invited.notify(inviter, self, NotificationAction.invite) | ||||
|         db.session.add(notification) | ||||
|  | ||||
|         return invite | ||||
|  | ||||
|  | ||||
| class UserSetting(db.Model):  # pylint: disable=too-few-public-methods | ||||
|     """Database model for user settings | ||||
| @@ -619,12 +543,6 @@ class UserFollow(db.Model):  # pylint: disable=too-few-public-methods | ||||
|     #: The timestamp when the follow was accepted | ||||
|     accepted_at = db.Column(db.DateTime(), nullable=True) | ||||
|  | ||||
|     def accept(self): | ||||
|         """Accept this follow request | ||||
|         """ | ||||
|  | ||||
|         self.accepted_at = datetime.utcnow() | ||||
|  | ||||
|  | ||||
| class Notification(db.Model): | ||||
|     """Database model for notifications | ||||
|   | ||||
| @@ -1,13 +0,0 @@ | ||||
| {% extends 'base.html' %} | ||||
|  | ||||
| {% block content %} | ||||
| <h2>{% trans %}Follow requests{% endtrans %}</h2> | ||||
| <ul> | ||||
|     {% for req in requests %} | ||||
|     <li> | ||||
|         {{ req.follower }} | ||||
|         <a href="{{ url_for('accept_follow', follower_id=req.follower_id) }}">{% trans %}Accept{% endtrans %}</a> | ||||
|     </li> | ||||
|     {% endfor %} | ||||
| </ul> | ||||
| {% endblock content %} | ||||
| @@ -120,7 +120,7 @@ | ||||
|     {%- endif %} | ||||
|             <td class="{% if day.month != calendar.timestamp.month %} other-month{% endif %}{% if day.date() == now.date() %} today{% endif %}"> | ||||
|                 <span class="day-num">{{ day.day }}</span> | ||||
|     {% for event in calendar.day_events(day, user=current_user if user_only else none) %} | ||||
|     {% for event in calendar.day_events(day, user=current_user) %} | ||||
|                 <a href="{{ url_for('event_details', event_uuid=event.event_uuid) }}" class="event"> | ||||
|                     {{ event.start_time_for_user(current_user).strftime('%H:%M') }}–{{ event.end_time_for_user(current_user).strftime('%H:%M') }} | ||||
|                     {{ event.title }} | ||||
|   | ||||
| @@ -2,10 +2,7 @@ | ||||
|  | ||||
| {% block content %} | ||||
| <h1> | ||||
|     {% if profile.locked %} | ||||
|     [locked] | ||||
|     {% endif %} | ||||
|     {{ profile.display_name }} | ||||
|     {{ profile.name }} | ||||
|     <small>@{{ profile.user.username}}</small> | ||||
| </h1> | ||||
|     {% if profile.user != current_user %} | ||||
|   | ||||
| @@ -1,22 +0,0 @@ | ||||
| {% extends 'settings-base.html' %} | ||||
|  | ||||
| {% block content %} | ||||
| {{ super() }} | ||||
| <h2>{% trans %}Edit profile{% endtrans %}</h2> | ||||
| <form method="post"> | ||||
|     {{ form.hidden_tag() }} | ||||
|     {{ form.errors }} | ||||
|  | ||||
|     {{ form.display_name.errors }} | ||||
|     {{ form.display_name.label }} | ||||
|     {{ form.display_name }} | ||||
|     <br> | ||||
|  | ||||
|     {{ form.locked.errors }} | ||||
|     {{ form.locked.label }} | ||||
|     {{ form.locked}} | ||||
|     <br> | ||||
|  | ||||
|     <button type="submit">{% trans %}Save{% endtrans %}</button> | ||||
| </form> | ||||
| {% endblock content %} | ||||
| @@ -1,10 +0,0 @@ | ||||
| {% extends 'base.html' %} | ||||
|  | ||||
| {% block content %} | ||||
| <nav> | ||||
|     <ul> | ||||
|         <li><a href="{{ url_for('edit_profile') }}">{% trans %}Edit profile{% endtrans %}</a></li> | ||||
|         <li><a href="{{ url_for('settings') }}">{% trans %}Settings{% endtrans %}</a></li> | ||||
|     </ul> | ||||
| </nav> | ||||
| {% endblock %} | ||||
| @@ -1,8 +1,6 @@ | ||||
| {% extends 'settings-base.html' %} | ||||
| {% extends 'base.html' %} | ||||
|  | ||||
| {% block content %} | ||||
| {{ super() }} | ||||
| <h2>{% trans %}Settings{% endtrans %}</h2> | ||||
| <form method="post"> | ||||
|     {{ form.hidden_tag() }} | ||||
|  | ||||
|   | ||||
| @@ -1,72 +0,0 @@ | ||||
| # Calendar.social | ||||
| # Copyright (C) 2018  Gergely Polonkai | ||||
| # | ||||
| # This program is free software: you can redistribute it and/or modify | ||||
| # it under the terms of the GNU Affero General Public License as published by | ||||
| # the Free Software Foundation, either version 3 of the License, or | ||||
| # (at your option) any later version. | ||||
| # | ||||
| # This program is distributed in the hope that it will be useful, | ||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| # GNU Affero General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU Affero General Public License | ||||
| # along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  | ||||
| """Helper functions and fixtures for testing | ||||
| """ | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| import calsocial | ||||
| from calsocial.models import db | ||||
|  | ||||
|  | ||||
| def configure_app(): | ||||
|     """Set default configuration values for testing | ||||
|     """ | ||||
|  | ||||
|     calsocial.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' | ||||
|     calsocial.app.config['TESTING'] = True | ||||
|     calsocial.app.config['WTF_CSRF_ENABLED'] = False | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def client(): | ||||
|     """Fixture that provides a Flask test client | ||||
|     """ | ||||
|     configure_app() | ||||
|     client = calsocial.app.test_client() | ||||
|  | ||||
|     with calsocial.app.app_context(): | ||||
|         db.create_all() | ||||
|  | ||||
|     yield client | ||||
|  | ||||
|     with calsocial.app.app_context(): | ||||
|         db.drop_all() | ||||
|  | ||||
|  | ||||
| def login(client, username, password, no_redirect=False): | ||||
|     """Login with the specified username and password | ||||
|     """ | ||||
|  | ||||
|     return client.post('/login', | ||||
|                        data={'email': username, 'password': password}, | ||||
|                        follow_redirects=not no_redirect) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def database(): | ||||
|     """Fixture to provide all database tables in an active application context | ||||
|     """ | ||||
|  | ||||
|     configure_app() | ||||
|  | ||||
|     with calsocial.app.app_context(): | ||||
|         db.create_all() | ||||
|  | ||||
|         yield db | ||||
|  | ||||
|         db.drop_all() | ||||
| @@ -1,23 +1,23 @@ | ||||
| # Calendar.social | ||||
| # Copyright (C) 2018  Gergely Polonkai | ||||
| # | ||||
| # This program is free software: you can redistribute it and/or modify | ||||
| # it under the terms of the GNU Affero General Public License as published by | ||||
| # the Free Software Foundation, either version 3 of the License, or | ||||
| # (at your option) any later version. | ||||
| # | ||||
| # This program is distributed in the hope that it will be useful, | ||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| # GNU Affero General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU Affero General Public License | ||||
| # along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
| import pytest | ||||
|  | ||||
| """General tests for Calendar.social | ||||
| """ | ||||
| import calsocial | ||||
| from calsocial.models import db, User | ||||
|  | ||||
| from helpers import client | ||||
|  | ||||
| @pytest.fixture | ||||
| def client(): | ||||
|     calsocial.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' | ||||
|     calsocial.app.config['TESTING'] = True | ||||
|     calsocial.app.config['WTF_CSRF_ENABLED'] = False | ||||
|     client = calsocial.app.test_client() | ||||
|  | ||||
|     with calsocial.app.app_context(): | ||||
|         db.create_all() | ||||
|  | ||||
|     yield client | ||||
|  | ||||
|     with calsocial.app.app_context(): | ||||
|         db.drop_all() | ||||
|  | ||||
|  | ||||
| def test_index_no_login(client): | ||||
| @@ -26,3 +26,96 @@ def test_index_no_login(client): | ||||
|  | ||||
|     page = client.get('/') | ||||
|     assert b'Welcome to Calendar.social' in page.data | ||||
|  | ||||
|  | ||||
| def test_register_page(client): | ||||
|     """Test the registration page | ||||
|     """ | ||||
|  | ||||
|     page = client.get('/register') | ||||
|     assert b'Register</button>' in page.data | ||||
|  | ||||
| def test_register_post_empty(client): | ||||
|     """Test sending empty registration data | ||||
|     """ | ||||
|  | ||||
|     page = client.post('/register', data={}) | ||||
|     assert b'This field is required' in page.data | ||||
|  | ||||
| def test_register_invalid_email(client): | ||||
|     """Test sending an invalid email address | ||||
|     """ | ||||
|  | ||||
|     page = client.post('/register', data={ | ||||
|         'username': 'test', | ||||
|         'email': 'test', | ||||
|         'password': 'password', | ||||
|         'password_retype': 'password', | ||||
|     }) | ||||
|     assert b'Invalid email address' in page.data | ||||
|  | ||||
| def test_register_password_mismatch(client): | ||||
|     """Test sending different password for registration | ||||
|     """ | ||||
|  | ||||
|     page = client.post('/register', data={ | ||||
|         'username': 'test', | ||||
|         'email': 'test@example.com', | ||||
|         'password': 'password', | ||||
|         'password_retype': 'something', | ||||
|     }) | ||||
|     assert b'The two passwords must match' in page.data | ||||
|  | ||||
| def test_register(client): | ||||
|     """Test user registration | ||||
|     """ | ||||
|  | ||||
|     page = client.post('/register', data={ | ||||
|         'username': 'test', | ||||
|         'email': 'test@example.com', | ||||
|         'password': 'password', | ||||
|         'password_retype': 'password', | ||||
|     }) | ||||
|     print(page.data) | ||||
|     assert page.status_code == 302 | ||||
|     assert page.location == 'http://localhost/' | ||||
|  | ||||
|     with calsocial.app.app_context(): | ||||
|         user = User.query.one() | ||||
|  | ||||
|     assert user.username == 'test' | ||||
|     assert user.email == 'test@example.com' | ||||
|  | ||||
| def test_register_existing_username(client): | ||||
|     """Test registering an existing username | ||||
|     """ | ||||
|  | ||||
|     with calsocial.app.app_context(): | ||||
|         user = User(username='test', email='test@example.com') | ||||
|         db.session.add(user) | ||||
|         db.session.commit() | ||||
|  | ||||
|     page = client.post('/register', data={ | ||||
|         'username': 'test', | ||||
|         'email': 'test2@example.com', | ||||
|         'password': 'password', | ||||
|         'password_retype': 'password', | ||||
|     }) | ||||
|     assert b'This username is not available' in page.data | ||||
|  | ||||
| def test_register_existing_email(client): | ||||
|     """Test registering an existing email address | ||||
|     """ | ||||
|  | ||||
|     with calsocial.app.app_context(): | ||||
|         user = User(username='test', email='test@example.com') | ||||
|         db.session.add(user) | ||||
|         db.session.commit() | ||||
|  | ||||
|     page = client.post('/register', data={ | ||||
|         'username': 'tester', | ||||
|         'email': 'test@example.com', | ||||
|         'password': 'password', | ||||
|         'password_retype': 'password', | ||||
|     }) | ||||
|     assert b'This email address can not be used' in page.data | ||||
|   | ||||
| @@ -1,117 +0,0 @@ | ||||
| # Calendar.social | ||||
| # Copyright (C) 2018  Gergely Polonkai | ||||
| # | ||||
| # This program is free software: you can redistribute it and/or modify | ||||
| # it under the terms of the GNU Affero General Public License as published by | ||||
| # the Free Software Foundation, either version 3 of the License, or | ||||
| # (at your option) any later version. | ||||
| # | ||||
| # This program is distributed in the hope that it will be useful, | ||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| # GNU Affero General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU Affero General Public License | ||||
| # along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  | ||||
| """Profile related tests for Calendar.social | ||||
| """ | ||||
|  | ||||
| import calsocial | ||||
| from calsocial.models import db, Notification, NotificationAction, Profile, User, UserFollow | ||||
|  | ||||
| from helpers import client, database, login | ||||
|  | ||||
|  | ||||
| def test_profile_follow(database): | ||||
|     """Test the Profile.follow() method | ||||
|     """ | ||||
|  | ||||
|     follower_user = User(username='follower', | ||||
|                          email='follower@example.com', | ||||
|                          password='passworder', | ||||
|                          active=True) | ||||
|     followed_user = User(username='followed', email='followed@example.com') | ||||
|     follower = Profile(display_name='Follower', user=follower_user) | ||||
|     followed = Profile(display_name='Followed', user=followed_user) | ||||
|     db.session.add_all([follower, followed]) | ||||
|     db.session.commit() | ||||
|  | ||||
|     user_follow = followed.follow(follower=follower) | ||||
|     db.session.commit() | ||||
|  | ||||
|     # The new follower record should have the fields set correctly | ||||
|     assert user_follow.followed == followed | ||||
|     assert user_follow.follower == follower | ||||
|  | ||||
|     # There should be a notification about the follow | ||||
|     notification = Notification.query.one() | ||||
|  | ||||
|     assert notification.actor == follower | ||||
|     assert notification.item == followed | ||||
|     assert notification.action == NotificationAction.follow | ||||
|  | ||||
|     assert follower in followed.follower_list | ||||
|     assert followed in follower.followed_list | ||||
|  | ||||
|  | ||||
| def test_follow_ui(client): | ||||
|     """Test following on the web interface | ||||
|     """ | ||||
|  | ||||
|     with calsocial.app.app_context(): | ||||
|         follower_user = User(username='follower', | ||||
|                              email='follower@example.com', | ||||
|                              password='passworder', | ||||
|                              active=True) | ||||
|         followed_user = User(username='followed', email='followed@example.com') | ||||
|         follower = Profile(display_name='Follower', user=follower_user) | ||||
|         followed = Profile(display_name='Followed', user=followed_user) | ||||
|         db.session.add_all([follower, followed]) | ||||
|         db.session.commit() | ||||
|  | ||||
|     login(client, 'follower', 'passworder') | ||||
|  | ||||
|     page = client.get('/profile/@followed/follow') | ||||
|     assert page.status_code == 302 | ||||
|     assert page.location == 'http://localhost/profile/%40followed' | ||||
|  | ||||
|     with calsocial.app.app_context(): | ||||
|         db.session.add_all([follower, followed]) | ||||
|         follow = UserFollow.query.one() | ||||
|         assert follow.follower == follower | ||||
|         assert follow.followed == followed | ||||
|         assert follow.accepted_at is not None | ||||
|  | ||||
|  | ||||
| def test_locked_profile(database): | ||||
|     """Test following a locked profile | ||||
|     """ | ||||
|  | ||||
|     follower_user = User(username='follower', | ||||
|                          email='follower@example.com', | ||||
|                          password='passworder', | ||||
|                          active=True) | ||||
|     followed_user = User(username='followed', email='followed@example.com') | ||||
|     follower = Profile(display_name='Follower', user=follower_user) | ||||
|     followed = Profile(display_name='Followed', user=followed_user, locked=True) | ||||
|     db.session.add_all([follower, followed]) | ||||
|     db.session.commit() | ||||
|  | ||||
|     user_follow = followed.follow(follower=follower) | ||||
|     db.session.commit() | ||||
|  | ||||
|     # The new follower record should have the fields set correctly | ||||
|     assert user_follow.followed == followed | ||||
|     assert user_follow.follower == follower | ||||
|     assert not user_follow.accepted_at | ||||
|  | ||||
|     # There should be a notification about the follow | ||||
|     notification = Notification.query.one() | ||||
|  | ||||
|     assert notification.actor == follower | ||||
|     assert notification.item == followed | ||||
|     assert notification.action == NotificationAction.follow | ||||
|  | ||||
|     assert follower not in followed.follower_list | ||||
|     assert followed not in follower.followed_list | ||||
| @@ -1,84 +0,0 @@ | ||||
| # Calendar.social | ||||
| # Copyright (C) 2018  Gergely Polonkai | ||||
| # | ||||
| # This program is free software: you can redistribute it and/or modify | ||||
| # it under the terms of the GNU Affero General Public License as published by | ||||
| # the Free Software Foundation, either version 3 of the License, or | ||||
| # (at your option) any later version. | ||||
| # | ||||
| # This program is distributed in the hope that it will be useful, | ||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| # GNU Affero General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU Affero General Public License | ||||
| # along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  | ||||
| """General tests for Calendar.social | ||||
| """ | ||||
|  | ||||
| import calsocial | ||||
| from calsocial.models import db, User | ||||
|  | ||||
| from helpers import client, login | ||||
|  | ||||
|  | ||||
| def test_index_no_login(client): | ||||
|     """Test the main page without logging in | ||||
|     """ | ||||
|  | ||||
|     page = client.get('/') | ||||
|     assert b'Welcome to Calendar.social' in page.data | ||||
|  | ||||
|  | ||||
| def test_login_invalid_user(client): | ||||
|     """Test logging in with a non-existing user | ||||
|     """ | ||||
|  | ||||
|     page = login(client, 'username', 'password') | ||||
|     assert b'Specified user does not exist' in page.data | ||||
|  | ||||
|  | ||||
| def test_login_bad_password(client): | ||||
|     """Test logging in with a bad password | ||||
|     """ | ||||
|  | ||||
|     with calsocial.app.app_context(): | ||||
|         user = User(username='test', email='test@example.com', password='password') | ||||
|         db.session.add(user) | ||||
|         db.session.commit() | ||||
|  | ||||
|     page = login(client, 'test', password='else') | ||||
|     assert b'Invalid password' in page.data | ||||
|  | ||||
|  | ||||
| def test_login_email(client): | ||||
|     """Test logging in with the email address instead of the username | ||||
|     """ | ||||
|  | ||||
|     with calsocial.app.app_context(): | ||||
|         user = User(username='test', email='test@example.com', password='password', active=True) | ||||
|         db.session.add(user) | ||||
|         db.session.commit() | ||||
|  | ||||
|     page = login(client, 'test@example.com', password='password') | ||||
|     assert b'Logged in as ' in page.data | ||||
|  | ||||
|  | ||||
| def test_login_first_steps(client): | ||||
|     """Test logging in with a new user | ||||
|  | ||||
|     They must be redirected to the first login page | ||||
|     """ | ||||
|  | ||||
|     with calsocial.app.app_context(): | ||||
|         user = User(username='test', email='test@example.com', password='password', active=True) | ||||
|         db.session.add(user) | ||||
|         db.session.commit() | ||||
|  | ||||
|     page = login(client, 'test', password='password', no_redirect=True) | ||||
|     # First, we must be redirected to the main page | ||||
|     assert page.location == 'http://localhost/' | ||||
|  | ||||
|     page = client.get('/') | ||||
|     assert page.location == 'http://localhost/first-steps' | ||||
| @@ -1,116 +0,0 @@ | ||||
| # Calendar.social | ||||
| # Copyright (C) 2018  Gergely Polonkai | ||||
| # | ||||
| # This program is free software: you can redistribute it and/or modify | ||||
| # it under the terms of the GNU Affero General Public License as published by | ||||
| # the Free Software Foundation, either version 3 of the License, or | ||||
| # (at your option) any later version. | ||||
| # | ||||
| # This program is distributed in the hope that it will be useful, | ||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| # GNU Affero General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU Affero General Public License | ||||
| # along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  | ||||
| """General tests for Calendar.social | ||||
| """ | ||||
|  | ||||
| import calsocial | ||||
| from calsocial.models import db, User | ||||
|  | ||||
| from helpers import client | ||||
|  | ||||
|  | ||||
| def test_register_page(client): | ||||
|     """Test the registration page | ||||
|     """ | ||||
|  | ||||
|     page = client.get('/register') | ||||
|     assert b'Register</button>' in page.data | ||||
|  | ||||
| def test_register_post_empty(client): | ||||
|     """Test sending empty registration data | ||||
|     """ | ||||
|  | ||||
|     page = client.post('/register', data={}) | ||||
|     assert b'This field is required' in page.data | ||||
|  | ||||
| def test_register_invalid_email(client): | ||||
|     """Test sending an invalid email address | ||||
|     """ | ||||
|  | ||||
|     page = client.post('/register', data={ | ||||
|         'username': 'test', | ||||
|         'email': 'test', | ||||
|         'password': 'password', | ||||
|         'password_retype': 'password', | ||||
|     }) | ||||
|     assert b'Invalid email address' in page.data | ||||
|  | ||||
| def test_register_password_mismatch(client): | ||||
|     """Test sending different password for registration | ||||
|     """ | ||||
|  | ||||
|     page = client.post('/register', data={ | ||||
|         'username': 'test', | ||||
|         'email': 'test@example.com', | ||||
|         'password': 'password', | ||||
|         'password_retype': 'something', | ||||
|     }) | ||||
|     assert b'The two passwords must match' in page.data | ||||
|  | ||||
| def test_register(client): | ||||
|     """Test user registration | ||||
|     """ | ||||
|  | ||||
|     page = client.post('/register', data={ | ||||
|         'username': 'test', | ||||
|         'email': 'test@example.com', | ||||
|         'password': 'password', | ||||
|         'password_retype': 'password', | ||||
|     }) | ||||
|     print(page.data) | ||||
|     assert page.status_code == 302 | ||||
|     assert page.location == 'http://localhost/' | ||||
|  | ||||
|     with calsocial.app.app_context(): | ||||
|         user = User.query.one() | ||||
|  | ||||
|     assert user.username == 'test' | ||||
|     assert user.email == 'test@example.com' | ||||
|  | ||||
| def test_register_existing_username(client): | ||||
|     """Test registering an existing username | ||||
|     """ | ||||
|  | ||||
|     with calsocial.app.app_context(): | ||||
|         user = User(username='test', email='test@example.com') | ||||
|         db.session.add(user) | ||||
|         db.session.commit() | ||||
|  | ||||
|     page = client.post('/register', data={ | ||||
|         'username': 'test', | ||||
|         'email': 'test2@example.com', | ||||
|         'password': 'password', | ||||
|         'password_retype': 'password', | ||||
|     }) | ||||
|     assert b'This username is not available' in page.data | ||||
|  | ||||
| def test_register_existing_email(client): | ||||
|     """Test registering an existing email address | ||||
|     """ | ||||
|  | ||||
|     with calsocial.app.app_context(): | ||||
|         user = User(username='test', email='test@example.com') | ||||
|         db.session.add(user) | ||||
|         db.session.commit() | ||||
|  | ||||
|     page = client.post('/register', data={ | ||||
|         'username': 'tester', | ||||
|         'email': 'test@example.com', | ||||
|         'password': 'password', | ||||
|         'password_retype': 'password', | ||||
|     }) | ||||
|     assert b'This email address can not be used' in page.data | ||||
		Reference in New Issue
	
	Block a user