forked from gergely/calendar-social
		
	Compare commits
	
		
			22 Commits
		
	
	
		
			drone-ci
			...
			model-upda
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 06c7b2ea52 | |||
| 3308be40ee | |||
| 9b01431641 | |||
| 2b1378310a | |||
| 5639c3f578 | |||
| 61f10f951c | |||
| 496b5b6c04 | |||
| dc0b2954c1 | |||
| 36c2f0fd77 | |||
| 27c78ff36f | |||
| 37e08fed22 | |||
| a0fba3f2af | |||
| 48a19a2296 | |||
| 5d886a7853 | |||
| 5550e5ecf3 | |||
| 0a3cfafef3 | |||
| 8e3bcd8ede | |||
| 48ffb0d472 | |||
| c3348d3212 | |||
| 1a69928241 | |||
| 7b935afdad | |||
| 303dd3d082 | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| __pycache__/ | ||||
| /calsocial/local.db | ||||
| /messages.pot | ||||
| /app/translations/*/LC_MESSAGES/*.mo | ||||
| /calsocial/translations/*/LC_MESSAGES/*.mo | ||||
| /.pytest_cache/ | ||||
|   | ||||
| @@ -170,7 +170,7 @@ class CalendarSocialApp(Flask): | ||||
|  | ||||
|         calendar = GregorianCalendar(timestamp.timestamp()) | ||||
|  | ||||
|         return render_template('index.html', calendar=calendar) | ||||
|         return render_template('index.html', calendar=calendar, user_only=True) | ||||
|  | ||||
|     @staticmethod | ||||
|     @route('/register', methods=['POST', 'GET']) | ||||
| @@ -217,7 +217,7 @@ class CalendarSocialApp(Flask): | ||||
|         form = EventForm() | ||||
|  | ||||
|         if form.validate_on_submit(): | ||||
|             event = Event(user=current_user) | ||||
|             event = Event(profile=current_user.profile) | ||||
|             form.populate_obj(event) | ||||
|  | ||||
|             db.session.add(event) | ||||
| @@ -255,7 +255,7 @@ class CalendarSocialApp(Flask): | ||||
|         """ | ||||
|  | ||||
|         from .forms import InviteForm | ||||
|         from .models import db, Event, Invitation, Notification, NotificationAction | ||||
|         from .models import db, Event | ||||
|  | ||||
|         try: | ||||
|             event = Event.query.filter(Event.event_uuid == event_uuid).one() | ||||
| @@ -267,15 +267,7 @@ class CalendarSocialApp(Flask): | ||||
|         form = InviteForm(event) | ||||
|  | ||||
|         if form.validate_on_submit(): | ||||
|             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) | ||||
|             event.invite(current_user.profile, invitee=form.invitee.data) | ||||
|  | ||||
|             db.session.commit() | ||||
|  | ||||
| @@ -300,11 +292,12 @@ 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, UserFollow, Notification, NotificationAction | ||||
|         from .models import db, Profile, User | ||||
|  | ||||
|         try: | ||||
|             profile = Profile.query.join(User).filter(User.username == username).one() | ||||
| @@ -312,16 +305,7 @@ class CalendarSocialApp(Flask): | ||||
|             abort(404) | ||||
|  | ||||
|         if profile.user != current_user: | ||||
|             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) | ||||
|             profile.follow(follower=current_user.profile) | ||||
|  | ||||
|             db.session.commit() | ||||
|  | ||||
| @@ -399,5 +383,83 @@ 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,8 +330,24 @@ 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 | ||||
|   | ||||
| @@ -103,6 +103,24 @@ class ResponseType(Enum): | ||||
|         return Enum.__eq__(self, other) | ||||
|  | ||||
|  | ||||
| class EventAvailability(Enum): | ||||
|     free = 0 | ||||
|     busy = 1 | ||||
|  | ||||
|  | ||||
| class UserAvailability(EventAvailability): | ||||
|     tentative = 2 | ||||
|  | ||||
|  | ||||
| class ResponseVisibility(Enum): | ||||
|     private = 0 | ||||
|     organisers = 1 | ||||
|     attendees = 2 | ||||
|     followers = 3 | ||||
|     friends = 4 | ||||
|     public = 5 | ||||
|  | ||||
|  | ||||
| class SettingsProxy: | ||||
|     """Proxy object to get settings for a user | ||||
|     """ | ||||
| @@ -246,6 +264,9 @@ 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 | ||||
| @@ -265,7 +286,7 @@ class Profile(db.Model):  # pylint: disable=too-few-public-methods | ||||
|             domain = '' | ||||
|         else: | ||||
|             username = self.username | ||||
|             domain = '@' + self.domain | ||||
|             domain = f'@{self.domain}' | ||||
|  | ||||
|         return f'<Profile {self.id}(@{username}{domain})>' | ||||
|  | ||||
| @@ -290,7 +311,8 @@ class Profile(db.Model):  # pylint: disable=too-few-public-methods | ||||
|  | ||||
|         return Profile.query \ | ||||
|             .join(UserFollow.followed) \ | ||||
|             .filter(UserFollow.follower == self) | ||||
|             .filter(UserFollow.follower == self) \ | ||||
|             .filter(UserFollow.accepted_at.isnot(None)) | ||||
|  | ||||
|     @property | ||||
|     def follower_list(self): | ||||
| @@ -303,7 +325,8 @@ class Profile(db.Model):  # pylint: disable=too-few-public-methods | ||||
|  | ||||
|         return Profile.query \ | ||||
|             .join(UserFollow.follower) \ | ||||
|             .filter(UserFollow.followed == self) | ||||
|             .filter(UserFollow.followed == self) \ | ||||
|             .filter(UserFollow.accepted_at.isnot(None)) | ||||
|  | ||||
|     @property | ||||
|     def url(self): | ||||
| @@ -317,6 +340,48 @@ 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 | ||||
| @@ -400,6 +465,20 @@ 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 | ||||
| @@ -543,6 +622,12 @@ 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 | ||||
|   | ||||
							
								
								
									
										13
									
								
								calsocial/templates/follow-requests.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								calsocial/templates/follow-requests.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| {% 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) %} | ||||
|     {% for event in calendar.day_events(day, user=current_user if user_only else none) %} | ||||
|                 <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,7 +2,10 @@ | ||||
|  | ||||
| {% block content %} | ||||
| <h1> | ||||
|     {{ profile.name }} | ||||
|     {% if profile.locked %} | ||||
|     [locked] | ||||
|     {% endif %} | ||||
|     {{ profile.display_name }} | ||||
|     <small>@{{ profile.user.username}}</small> | ||||
| </h1> | ||||
|     {% if profile.user != current_user %} | ||||
|   | ||||
							
								
								
									
										22
									
								
								calsocial/templates/profile-edit.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								calsocial/templates/profile-edit.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| {% 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 %} | ||||
							
								
								
									
										10
									
								
								calsocial/templates/settings-base.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								calsocial/templates/settings-base.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| {% 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,6 +1,8 @@ | ||||
| {% extends 'base.html' %} | ||||
| {% extends 'settings-base.html' %} | ||||
|  | ||||
| {% block content %} | ||||
| {{ super() }} | ||||
| <h2>{% trans %}Settings{% endtrans %}</h2> | ||||
| <form method="post"> | ||||
|     {{ form.hidden_tag() }} | ||||
|  | ||||
|   | ||||
							
								
								
									
										72
									
								
								tests/helpers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								tests/helpers.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| # 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 @@ | ||||
| import pytest | ||||
| # 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 calsocial | ||||
| from calsocial.models import db, User | ||||
| """General tests for Calendar.social | ||||
| """ | ||||
|  | ||||
|  | ||||
| @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() | ||||
| from helpers import client | ||||
|  | ||||
|  | ||||
| def test_index_no_login(client): | ||||
| @@ -26,96 +26,3 @@ 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 | ||||
|   | ||||
							
								
								
									
										117
									
								
								tests/test_follow.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								tests/test_follow.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| # 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 | ||||
							
								
								
									
										84
									
								
								tests/test_login.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								tests/test_login.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| # 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' | ||||
							
								
								
									
										116
									
								
								tests/test_register.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								tests/test_register.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
| # 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