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