28 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/eventedit.html:
--------------------------------------------------------------------------------
1 |
30 |
31 |
--------------------------------------------------------------------------------
/app/templates/eventview.html:
--------------------------------------------------------------------------------
1 |
36 |
--------------------------------------------------------------------------------
/app/templates/four_o_four.j2:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %} - 404{% endblock %}
4 |
5 | {% block content %}
6 |
7 |
11 |
12 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/friendview.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block head %}
3 | {{ super() }}
4 |
29 | {% endblock %}
30 |
--------------------------------------------------------------------------------
/app/templates/hello.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
5 |
8 |
9 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/home.html:
--------------------------------------------------------------------------------
1 | {% extends "partials/index/index_base.html" %}
2 |
3 | {% block content %}
4 |
5 |
6 |
31 |
32 | {% endblock %}
33 |
--------------------------------------------------------------------------------
/app/templates/import_holidays.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 |
4 | {% block content %}
5 |
6 |
17 |
18 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 |
5 |
43 |
44 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/invitations.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 |
4 | {% block content %}
5 |
6 |
22 | {% endif %}
23 |
24 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/invite_mail.html:
--------------------------------------------------------------------------------
1 | {% extends "mail_base.html" %}
2 |
3 |
4 | {% block content %}
5 |
6 |
14 |
15 |
16 | {% endblock %}
17 |
--------------------------------------------------------------------------------
/app/templates/login.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block content %}
3 |
24 | {% endblock %}
25 |
--------------------------------------------------------------------------------
/app/templates/on_this_day.html:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/app/templates/partials/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {% block head %}
5 |
16 |
17 |
18 |
19 |
22 |
23 |
24 |
27 |
30 |
31 | {% endblock head %}
32 | {% block title %}
33 |
34 | {% endblock %}
35 |
36 |
37 | {% block body %}
38 | {% endblock %}
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/templates/partials/calendar/calendar_base.html:
--------------------------------------------------------------------------------
1 | {% extends "./partials/base.html" %}
2 | {% block head %}
3 | {{super()}}
4 |
5 |
6 |
7 |
8 | {% endblock head %}
9 | {% block page_name %}Month View{% endblock page_name %}
10 | {% block body %}
11 |
21 |
22 |
23 |
24 |
25 | {% endblock body %}
--------------------------------------------------------------------------------
/app/templates/partials/calendar/event/comments_tab.html:
--------------------------------------------------------------------------------
1 |
22 | {% endfor %}
--------------------------------------------------------------------------------
/app/templates/partials/calendar/event/text_editor_partial_body.html:
--------------------------------------------------------------------------------
1 |
2 |
4 |
--------------------------------------------------------------------------------
/app/templates/partials/calendar/event/text_editor_partial_head.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/templates/partials/calendar/event/view_event_details_tab.html:
--------------------------------------------------------------------------------
1 |
51 |
--------------------------------------------------------------------------------
/app/templates/partials/calendar/feature_settings/example.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/templates/partials/calendar/monthly_view/add_week.html:
--------------------------------------------------------------------------------
1 | {% for week in weeks_block %}
2 |
21 | {% endfor %}
--------------------------------------------------------------------------------
/app/templates/partials/calendar/monthly_view/monthly_grid.html:
--------------------------------------------------------------------------------
1 |
34 |
--------------------------------------------------------------------------------
/app/templates/partials/calendar/navigation.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/templates/partials/index/index_base.html:
--------------------------------------------------------------------------------
1 | {% extends "partials/base.html" %}
2 | {% block head %}
3 | {{ super() }}
4 |
5 |
6 | {% endblock head %}
7 | {% block body %}
8 | {% include 'partials/index/navigation.html' %}
9 |
10 | {% block content %}
11 | {% endblock %}
12 |
13 | {% endblock body %}
--------------------------------------------------------------------------------
/app/templates/partials/index/navigation.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/templates/salary/month.j2:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
38 | {% endblock content %}
--------------------------------------------------------------------------------
/app/templates/salary/pick.j2:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
35 | {% endblock content %}
--------------------------------------------------------------------------------
/app/templates/salary/view.j2:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
44 | {% endblock content %}
--------------------------------------------------------------------------------
/app/templates/search.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 |
4 | {% block content %}
5 |
6 |
31 |
32 | Showing results for '{{ keywords }}':
33 |
34 |
35 |
36 |
37 | {% for result in results %}
38 |
39 |
40 |
45 |
46 |
47 | {{ result.content }}
48 |
49 |
50 |
{{ result.date }}
51 |
52 |
53 |
54 | {% endfor %}
55 |
56 |
57 | {% endif %}
58 |
59 |
60 |
70 |
71 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/share_event.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
10 |
11 |
12 |
13 |
14 |
15 |
Hi!
16 |
{{ msg_info.sender_name }} want you to know about event he have,
17 |
18 |
19 | The event {{ msg_info.event.title }}
20 |
21 |
22 | will take place from {{ msg_info.event.start_date }} until {{ msg_info.event.end_date }}
23 |
24 |
25 | {% if msg_info.event.location is not none %}
26 | in {{ msg_info.event.location }}
27 | {% endif %}
28 | {% if msg_info.event.vc_link is not none %}
29 |
Link to vc here
30 | {% endif %}
31 |
32 |
33 |
34 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/templates/weekview.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
weekview
11 |
12 |
13 |
14 |
15 | {% for day, dayview, events_and_attr in week %}
16 |
17 |
{{ day.strftime('%A').upper()[:3] }}
18 | {% set month = day.month %}
19 | {% set day = day.day %}
20 | {% set events = events_and_attr%}
21 | {% include dayview.template %}
22 |
23 | {% endfor %}
24 |
25 |
26 | {% for hour in range(24)%}
27 |
28 |
29 | {% set hour = hour|string() %}
30 | {{hour.zfill(2)}}:00
31 |
32 |
33 | {% endfor %}
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonFreeCourse/calendar/23a33703a0038d0eae8ce7299a93ad172c8f68e9/app/utils/__init__.py
--------------------------------------------------------------------------------
/app/utils/extending_openapi.py:
--------------------------------------------------------------------------------
1 | from fastapi.openapi.utils import get_openapi
2 |
3 |
4 | def custom_openapi(app):
5 | if app.openapi_schema:
6 | return app.openapi_schema
7 | url = ('https://forums.pythonic.guru'
8 | '/uploads/default/original/1X/'
9 | '3c7e2ccc77e214fb4e38daa421f1b8878a5677f9.jpeg')
10 | openapi_schema = get_openapi(
11 | title="Pylander API",
12 | version="1.0.0",
13 | description="This is a custom OpenAPI schema for Pylander Developers",
14 | routes=app.routes,
15 | )
16 | openapi_schema["info"]["x-logo"] = {
17 | # TODO: change logo when we have one
18 | "url": url
19 | }
20 | app.openapi_schema = openapi_schema
21 | app.openapiv = app.openapi_schema
22 |
--------------------------------------------------------------------------------
/git:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonFreeCourse/calendar/23a33703a0038d0eae8ce7299a93ad172c8f68e9/git
--------------------------------------------------------------------------------
/mypy.ini:
--------------------------------------------------------------------------------
1 | # Mypy configuration
2 | [mypy]
3 |
4 | # The following is needed to disable Mypy reporting the following error:
5 | # Skipping analyzing X: found module but no type hints or library stubs
6 | [mypy-arrow.*]
7 | ignore_missing_imports = True
8 |
9 | [mypy-email_validator.*]
10 | ignore_missing_imports = True
11 |
12 | [mypy-fastapi_mail.*]
13 | ignore_missing_imports = True
14 |
15 | [mypy-icalendar.*]
16 | ignore_missing_imports = True
17 |
18 | [mypy-iso639.*]
19 | ignore_missing_imports = True
20 |
21 | [mypy-nltk.*]
22 | ignore_missing_imports = True
23 |
24 | [mypy-passlib.*]
25 | ignore_missing_imports = True
26 |
27 | [mypy-PIL.*]
28 | ignore_missing_imports = True
29 |
30 | [mypy-textblob.*]
31 | ignore_missing_imports = True
32 |
33 | [mypy-responses.*]
34 | ignore_missing_imports = True
35 |
36 | [mypy-sqlalchemy.*]
37 | ignore_missing_imports = True
38 |
39 | [mypy-word_forms.*]
40 | ignore_missing_imports = True
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.black]
2 | line-length = 79
3 |
--------------------------------------------------------------------------------
/schema.md:
--------------------------------------------------------------------------------
1 | .
2 | ├── app
3 | │ ├── __init__.py
4 | │ ├── dependencies.py
5 | │ ├── main.py
6 | │ ├── database
7 | │ ├── __init__.py
8 | │ ├── database.py
9 | │ ├── models.py
10 | │ ├── schemas.py
11 | │ ├── internal
12 | │ ├── __init__.py
13 | │ ├── admin.py
14 | │ ├── agenda_events.py
15 | │ ├── email.py
16 | │ ├── media
17 | │ ├── example.png
18 | │ ├── fake_user.png
19 | │ ├── profile.png
20 | │ ├── routers
21 | │ ├── __init__.py
22 | │ ├── agenda.py
23 | │ ├── categories.py
24 | │ ├── email.py
25 | │ ├── event.py
26 | │ ├── profile.py
27 | │ ├── static
28 | │ ├── event
29 | │ ├── eventedit.css
30 | │ ├── eventview.css
31 | │ ├── agenda_style.css
32 | │ ├── popover.js
33 | │ ├── style.css
34 | │ ├── templates
35 | │ ├── base.html
36 | │ ├── home.html
37 | │ ├── profile.html
38 | ├── LICENSE
39 | ├── requirements.txt
40 | ├── schema.md
41 | └── tests
42 | ├── __init__.py
43 | ├── conftest.py
44 | ├── test_agenda_internal.py
45 | ├── test_agenda_route.py
46 | ├── test_app.py
47 | ├── test_categories.py
48 | ├── test_email.py
49 | ├── test_event.py
50 | └── test_profile.py
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonFreeCourse/calendar/23a33703a0038d0eae8ce7299a93ad172c8f68e9/tests/__init__.py
--------------------------------------------------------------------------------
/tests/association_fixture.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from sqlalchemy.orm import Session
3 |
4 | from app.database.models import Event, UserEvent
5 |
6 |
7 | @pytest.fixture
8 | def association(event: Event, session: Session) -> UserEvent:
9 | return (
10 | session.query(UserEvent)
11 | .filter(UserEvent.event_id == event.id)
12 | ).first()
13 |
--------------------------------------------------------------------------------
/tests/asyncio_fixture.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime, timedelta
2 |
3 | from httpx import AsyncClient
4 | import pytest
5 |
6 | from app.database.models import Base
7 | from app.main import app
8 | from app.routers import telegram
9 | from app.routers.event import create_event
10 | from tests.client_fixture import get_test_placeholder_user
11 | from tests.conftest import get_test_db, test_engine
12 |
13 |
14 | @pytest.fixture
15 | async def telegram_client():
16 | Base.metadata.create_all(bind=test_engine)
17 | app.dependency_overrides[telegram.get_db] = get_test_db
18 | async with AsyncClient(app=app, base_url="http://test") as ac:
19 | yield ac
20 | app.dependency_overrides = {}
21 | Base.metadata.drop_all(bind=test_engine)
22 |
23 |
24 | today_date = datetime.today().replace(hour=0, minute=0, second=0)
25 |
26 |
27 | @pytest.fixture
28 | def fake_user_events(session):
29 | Base.metadata.create_all(bind=test_engine)
30 | user = get_test_placeholder_user()
31 | session.add(user)
32 | session.commit()
33 | create_event(
34 | db=session,
35 | title='Cool today event',
36 | start=today_date,
37 | end=today_date + timedelta(days=2),
38 | all_day=False,
39 | content='test event',
40 | owner_id=user.id,
41 | location="Here",
42 | is_google_event=False,
43 | )
44 | create_event(
45 | db=session,
46 | title='Cool (somewhen in two days) event',
47 | start=today_date + timedelta(days=1),
48 | end=today_date + timedelta(days=3),
49 | all_day=False,
50 | content='this week test event',
51 | owner_id=user.id,
52 | location="Here",
53 | is_google_event=False,
54 | )
55 | yield user
56 | Base.metadata.drop_all(bind=test_engine)
57 |
--------------------------------------------------------------------------------
/tests/calendar-linux.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "calendar#events",
3 | "etag": "etag",
4 | "summary": "string",
5 | "description": "string",
6 | "updated": "2021-01-13T09:10:02.000Z",
7 | "timeZone": "string",
8 | "accessRole": "string",
9 | "defaultReminders": [
10 | {
11 | "method": "string",
12 | "minutes": 5
13 | }
14 | ],
15 | "nextPageToken": "string",
16 | "nextSyncToken": "string",
17 | "items": [
18 | {
19 | "kind": "calendar#event",
20 | "etag": "somecode",
21 | "id": "somecode",
22 | "status": "confirmed",
23 | "htmlLink": "https://www.google.com/calendar/event?eid=somecode",
24 | "created": "2021-01-13T09:10:02.000Z",
25 | "updated": "2021-01-13T09:10:02.388Z",
26 | "summary": "some title",
27 | "creator": {
28 | "email": "someemail",
29 | "self": true
30 | },
31 | "organizer": {
32 | "email": "someemail",
33 | "self": true
34 | },
35 | "start": {
36 | "dateTime": "2021-02-25T13:00:00+02:00"
37 | },
38 | "end": {
39 | "dateTime": "2021-02-25T14:00:00+02:00"
40 | },
41 | "iCalUID": "somecode",
42 | "sequence": 0,
43 | "reminders": {
44 | "useDefault": true
45 | }
46 | },
47 | {
48 | "kind": "calendar#event",
49 | "etag": "somecode",
50 | "id": "somecode",
51 | "status": "confirmed",
52 | "htmlLink": "https://www.google.com/calendar/event?eid=somecode",
53 | "created": "2021-01-13T09:10:02.000Z",
54 | "updated": "2021-01-13T09:10:02.388Z",
55 | "summary": "some title to all day event",
56 | "creator": {
57 | "email": "someemail",
58 | "self": true
59 | },
60 | "organizer": {
61 | "email": "someemail",
62 | "self": true
63 | },
64 | "start": {
65 | "date": "2021-02-25"
66 | },
67 | "end": {
68 | "date": "2021-02-25"
69 | },
70 | "iCalUID": "somecode",
71 | "sequence": 0,
72 | "location": "somelocation",
73 | "reminders": {
74 | "useDefault": true
75 | }
76 | }
77 | ]
78 | }
--------------------------------------------------------------------------------
/tests/category_fixture.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from sqlalchemy.orm import Session
3 |
4 | from app.database.models import Category, User
5 |
6 |
7 | @pytest.fixture
8 | def category(session: Session, sender: User) -> Category:
9 | category = Category.create(session, name="Guitar Lesson", color="121212",
10 | user_id=sender.id)
11 | yield category
12 | session.delete(category)
13 | session.commit()
14 |
--------------------------------------------------------------------------------
/tests/comment_fixture.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from typing import Iterator
3 |
4 | import pytest
5 | from sqlalchemy.orm.session import Session
6 |
7 | from app.database.models import Comment, Event, User
8 | from app.internal.utils import create_model, delete_instance
9 |
10 |
11 | @pytest.fixture
12 | def comment(session: Session, event: Event, user: User) -> Iterator[Comment]:
13 | data = {
14 | 'user': user,
15 | 'event': event,
16 | 'content': 'test comment',
17 | 'time': datetime(2021, 1, 1, 0, 1),
18 | }
19 | create_model(session, Comment, **data)
20 | comment = session.query(Comment).first()
21 | yield comment
22 | delete_instance(session, comment)
23 |
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | import calendar
2 |
3 | import pytest
4 | from sqlalchemy import create_engine
5 | from sqlalchemy.orm import sessionmaker
6 |
7 | from app.config import PSQL_ENVIRONMENT
8 | from app.database.models import Base
9 |
10 | pytest_plugins = [
11 | 'tests.user_fixture',
12 | 'tests.event_fixture',
13 | 'tests.dayview_fixture',
14 | 'tests.invitation_fixture',
15 | 'tests.association_fixture',
16 | 'tests.client_fixture',
17 | 'tests.asyncio_fixture',
18 | 'tests.logger_fixture',
19 | 'tests.category_fixture',
20 | 'smtpdfix',
21 | 'tests.quotes_fixture',
22 | 'tests.zodiac_fixture',
23 | 'tests.comment_fixture',
24 | ]
25 |
26 | # When testing in a PostgreSQL environment please make sure that:
27 | # - Base string is a PSQL string
28 | # - app.config.PSQL_ENVIRONMENT is set to True
29 |
30 | if PSQL_ENVIRONMENT:
31 | SQLALCHEMY_TEST_DATABASE_URL = (
32 | "postgresql://postgres:1234"
33 | "@localhost/postgres"
34 | )
35 | test_engine = create_engine(
36 | SQLALCHEMY_TEST_DATABASE_URL
37 | )
38 |
39 | else:
40 | SQLALCHEMY_TEST_DATABASE_URL = "sqlite:///./test.db"
41 | test_engine = create_engine(
42 | SQLALCHEMY_TEST_DATABASE_URL, connect_args={"check_same_thread": False}
43 | )
44 |
45 | TestingSessionLocal = sessionmaker(
46 | autocommit=False, autoflush=False, bind=test_engine)
47 |
48 |
49 | def get_test_db():
50 | return TestingSessionLocal()
51 |
52 |
53 | @pytest.fixture
54 | def session():
55 | Base.metadata.create_all(bind=test_engine)
56 | session = get_test_db()
57 | yield session
58 | session.rollback()
59 | session.close()
60 | Base.metadata.drop_all(bind=test_engine)
61 |
62 |
63 | @pytest.fixture
64 | def sqlite_engine():
65 | SQLALCHEMY_TEST_DATABASE_URL = "sqlite:///./test.db"
66 | sqlite_test_engine = create_engine(
67 | SQLALCHEMY_TEST_DATABASE_URL, connect_args={"check_same_thread": False}
68 | )
69 |
70 | TestingSession = sessionmaker(
71 | autocommit=False, autoflush=False, bind=sqlite_test_engine)
72 |
73 | yield sqlite_test_engine
74 | session = TestingSession()
75 | session.close()
76 | Base.metadata.drop_all(bind=sqlite_test_engine)
77 |
78 |
79 | @pytest.fixture
80 | def Calendar():
81 | return calendar.Calendar(0)
82 |
--------------------------------------------------------------------------------
/tests/dayview_fixture.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 | import pytest
4 |
5 | from app.database.models import Event
6 |
7 |
8 | @pytest.fixture
9 | def event1():
10 | start = datetime(year=2021, month=2, day=1, hour=7, minute=5)
11 | end = datetime(year=2021, month=2, day=1, hour=9, minute=15)
12 | return Event(title='test1', content='test',
13 | start=start, end=end, owner_id=1)
14 |
15 |
16 | @pytest.fixture
17 | def event2():
18 | start = datetime(year=2021, month=2, day=1, hour=13, minute=13)
19 | end = datetime(year=2021, month=2, day=1, hour=15, minute=46)
20 | return Event(title='test2', content='test',
21 | start=start, end=end, owner_id=1, color='blue')
22 |
23 |
24 | @pytest.fixture
25 | def event3():
26 | start = datetime(year=2021, month=2, day=3, hour=7, minute=5)
27 | end = datetime(year=2021, month=2, day=3, hour=9, minute=15)
28 | return Event(title='test3', content='test',
29 | start=start, end=end, owner_id=1)
30 |
31 |
32 | @pytest.fixture
33 | def all_day_event1():
34 | start = datetime(year=2021, month=2, day=3, hour=7, minute=5)
35 | end = datetime(year=2021, month=2, day=3, hour=9, minute=15)
36 | return Event(title='test3', content='test', all_day=True,
37 | start=start, end=end, owner_id=1)
38 |
39 |
40 | @pytest.fixture
41 | def small_event():
42 | start = datetime(year=2021, month=2, day=3, hour=7)
43 | end = datetime(year=2021, month=2, day=3, hour=8, minute=30)
44 | return Event(title='test3', content='test',
45 | start=start, end=end, owner_id=1)
46 |
47 |
48 | @pytest.fixture
49 | def event_with_no_minutes_modified():
50 | start = datetime(year=2021, month=2, day=3, hour=7)
51 | end = datetime(year=2021, month=2, day=3, hour=8)
52 | return Event(title='test_no_modify', content='test',
53 | start=start, end=end, owner_id=1)
54 |
55 |
56 | @pytest.fixture
57 | def multiday_event():
58 | start = datetime(year=2021, month=2, day=1, hour=13)
59 | end = datetime(year=2021, month=2, day=3, hour=13)
60 | return Event(title='test_multiday', content='test',
61 | start=start, end=end, owner_id=1, color='blue')
62 |
63 |
64 | @pytest.fixture
65 | def weekdays():
66 | return [
67 | 'Sunday', 'Monday', 'Tuesday',
68 | 'Wednesday', 'Thursday', 'Friday', 'Saturday',
69 | ]
70 |
71 |
72 | @pytest.fixture
73 | def sunday():
74 | return datetime(day=3, month=1, year=2021)
75 |
--------------------------------------------------------------------------------
/tests/files_for_import_file_tests/sample.ics:
--------------------------------------------------------------------------------
1 | BEGIN:VCALENDAR
2 | VERSION:2.0
3 | CALSCALE:GREGORIAN
4 | BEGIN:VEVENT
5 | SUMMARY:HeadA
6 | DTSTART;TZID=America/New_York:20190802T103400
7 | DTEND;TZID=America/New_York:20190802T110400
8 | LOCATION:Tel-Aviv
9 | DESCRIPTION:Content1
10 | STATUS:CONFIRMED
11 | SEQUENCE:3
12 | BEGIN:VALARM
13 | TRIGGER:-PT10M
14 | DESCRIPTION:desc_1
15 | ACTION:DISPLAY
16 | END:VALARM
17 | END:VEVENT
18 | BEGIN:VEVENT
19 | SUMMARY:HeadB
20 | DTSTART;TZID=America/New_York:20190802T200000
21 | DTEND;TZID=America/New_York:20190802T203000
22 | LOCATION:Tel-Aviv
23 | DESCRIPTION:Content2
24 | STATUS:CONFIRMED
25 | SEQUENCE:3
26 | BEGIN:VALARM
27 | TRIGGER:-PT10M
28 | DESCRIPTION:desc_2
29 | ACTION:DISPLAY
30 | END:VALARM
31 | END:VEVENT
32 | END:VCALENDAR
--------------------------------------------------------------------------------
/tests/files_for_import_file_tests/sample2.blabla:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonFreeCourse/calendar/23a33703a0038d0eae8ce7299a93ad172c8f68e9/tests/files_for_import_file_tests/sample2.blabla
--------------------------------------------------------------------------------
/tests/files_for_import_file_tests/sample2.ics:
--------------------------------------------------------------------------------
1 | BEGIN:VCALENDAR
2 | VERSION:2.0
3 | CALSCALE:GREGORIAN
4 |
5 | SUMMARY:HeadA
6 | DTSTART;TZID=America/New_York:20190802T103400
7 | DTEND;TZID=America/New_York:20190802T110400
8 | LOCATION:Tel-Aviv
9 | DESCRIPTION:Content1
10 | STATUS:CONFIRMED
11 | SEQUENCE:3
12 | BEGIN:VALARM
13 | TRIGGER:-PT10M
14 | DESCRIPTION:desc_1
15 | ACTION:DISPLAY
16 | END:VALARM
17 | END:VEVENT
18 | BEGIN:VEVENT
19 | SUMMARY:HeadB
20 | DTSTART;TZID=America/New_York:20190802T200000
21 | DTEND;TZID=America/New_York:20190802T203000
22 | LOCATION:Tel-Aviv
23 | DESCRIPTION:Content2
24 | STATUS:CONFIRMED
25 | SEQUENCE:3
26 | BEGIN:VALARM
27 | TRIGGER:-PT10M
28 | DESCRIPTION:desc_2
29 | ACTION:DISPLAY
30 | END:VALARM
31 | END:VEVENT
32 | END:VCALENDAR
--------------------------------------------------------------------------------
/tests/files_for_import_file_tests/sample3.ics:
--------------------------------------------------------------------------------
1 | BEGIN:VCALENDAR
2 | VERSION:2.0
3 | CALSCALE:GREGORIAN
4 | BEGIN:VEVENT
5 |
6 |
7 |
8 | LOCATION:Tel-Aviv
9 | DESCRIPTION:Content1
10 | STATUS:CONFIRMED
11 | SEQUENCE:3
12 | BEGIN:VALARM
13 | TRIGGER:-PT10M
14 | DESCRIPTION:desc_1
15 | ACTION:DISPLAY
16 | END:VALARM
17 | END:VEVENT
18 | BEGIN:VEVENT
19 | SUMMARY:HeadB
20 | DTSTART;TZID=America/New_York:20190802T200000
21 | DTEND;TZID=America/New_York:20190802T203000
22 | LOCATION:Tel-Aviv
23 | DESCRIPTION:Content2
24 | STATUS:CONFIRMED
25 | SEQUENCE:3
26 | BEGIN:VALARM
27 | TRIGGER:-PT10M
28 | DESCRIPTION:desc_2
29 | ACTION:DISPLAY
30 | END:VALARM
31 | END:VEVENT
32 | END:VCALENDAR
--------------------------------------------------------------------------------
/tests/files_for_import_file_tests/sample_calendar_data.csv:
--------------------------------------------------------------------------------
1 | Head1, Content1, 05-21-2019, 05-21-2019
2 | Head2, Content2, 01-11-2010, 01-11-2010
3 | Head3, Content3, 02-02-2022, 02-02-2022
--------------------------------------------------------------------------------
/tests/files_for_import_file_tests/sample_calendar_data.txt:
--------------------------------------------------------------------------------
1 | Head1, Content1, 05-21-2019, 05-21-2019
2 | Head2, Content2, 01-11-2010, 01-11-2010
3 | Head3, Content3, 02-02-2022, 02-02-2022
--------------------------------------------------------------------------------
/tests/files_for_import_file_tests/sample_data_invalid.txt:
--------------------------------------------------------------------------------
1 | Head1, Content1, 05-21-2019, 05-21-2019
2 | , Content2, 01-11-2010, 01-11-2010
3 | Head3, Content3, 02-02-2022, 02-02-2022
--------------------------------------------------------------------------------
/tests/files_for_import_file_tests/sample_date2_ver.txt:
--------------------------------------------------------------------------------
1 | Option1, Content1, 05-21-2019 10:30, 05-21-2019 11:30
2 | Option2, Content2, 01-11-2010 11:30, 01-11-2010 12:30
3 | Option3, Content3, 02-02-2022 13:00, 02-02-2022 13:05
--------------------------------------------------------------------------------
/tests/files_for_import_file_tests/sample_date_mix.txt:
--------------------------------------------------------------------------------
1 | Option1, Content1, 05-21-2019, 05-21-2019 11:30
2 | Option2, Content2, 01-11-2010 11:30, 01-11-2010 12:30
3 | Option3, Content3, 02-02-2022 13:00, 02-02-2022 13:05
--------------------------------------------------------------------------------
/tests/files_for_import_file_tests/sample_rng_invalid.txt:
--------------------------------------------------------------------------------
1 | Head1, Content1, 05-21-1990, 05-21-1990
2 | Head2, Content2, 01-11-2010, 01-11-2010
3 | Head3, Content3, 02-02-2022, 02-02-2022
--------------------------------------------------------------------------------
/tests/files_for_import_file_tests/sample_loc_ver.txt:
--------------------------------------------------------------------------------
1 | Option1, Content1, 05-21-2019 10:30, 05-21-2019 11:30, aaa
2 | Option2, Content2, 01-11-2010 11:30, 01-11-2010 12:30, bbb
3 | Option3, Content3, 02-02-2022 13:00, 02-02-2022 13:05, ccc
--------------------------------------------------------------------------------
/tests/invitation_fixture.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from typing import Generator
3 |
4 | import pytest
5 | from sqlalchemy.orm import Session
6 |
7 | from app.database.models import Event, Invitation, User
8 | from app.internal.utils import create_model, delete_instance
9 |
10 |
11 | @pytest.fixture
12 | def invitation(
13 | event: Event, user: User, session: Session
14 | ) -> Generator[Invitation, None, None]:
15 | """Returns an Invitation object after being created in the database.
16 |
17 | Args:
18 | event: An Event instance.
19 | user: A user instance.
20 | session: A database connection.
21 |
22 | Returns:
23 | An Invitation object.
24 | """
25 | invitation = create_model(
26 | session, Invitation,
27 | creation=datetime.now(),
28 | recipient=user,
29 | event=event,
30 | event_id=event.id,
31 | recipient_id=user.id,
32 | )
33 | yield invitation
34 | delete_instance(session, invitation)
35 |
--------------------------------------------------------------------------------
/tests/logger_fixture.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from _pytest.logging import caplog as _caplog # noqa: F401
4 | from loguru import logger
5 | import pytest
6 |
7 | from app import config
8 | from app.internal.logger_customizer import LoggerCustomizer
9 |
10 |
11 | @pytest.fixture(scope='module')
12 | def logger_instance():
13 | _logger = LoggerCustomizer.make_logger(config.LOG_PATH,
14 | config.LOG_FILENAME,
15 | config.LOG_LEVEL,
16 | config.LOG_ROTATION_INTERVAL,
17 | config.LOG_RETENTION_INTERVAL,
18 | config.LOG_FORMAT)
19 |
20 | return _logger
21 |
22 |
23 | @pytest.fixture
24 | def caplog(_caplog): # noqa: F811
25 | class PropagateHandler(logging.Handler):
26 | def emit(self, record):
27 | logging.getLogger(record.name).handle(record)
28 |
29 | handler_id = logger.add(PropagateHandler(), format="{message} {extra}")
30 | yield _caplog
31 | logger.remove(handler_id)
32 |
--------------------------------------------------------------------------------
/tests/quotes_fixture.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from sqlalchemy.orm import Session
3 |
4 | from app.database.models import Quote
5 | from app.internal.utils import create_model, delete_instance
6 |
7 |
8 | def add_quote(
9 | session: Session, id_quote: int, text: str, author: str
10 | ) -> Quote:
11 | quote = create_model(
12 | session,
13 | Quote,
14 | id=id_quote,
15 | text=text,
16 | author=author,
17 | )
18 | yield quote
19 | delete_instance(session, quote)
20 |
21 |
22 | @pytest.fixture
23 | def quote1(session: Session) -> Quote:
24 | yield from add_quote(
25 | session=session,
26 | id_quote=1,
27 | text='You have to believe in yourself.',
28 | author='Sun Tzu',
29 | )
30 |
31 |
32 | @pytest.fixture
33 | def quote2(session: Session) -> Quote:
34 | yield from add_quote(
35 | session=session,
36 | id_quote=2,
37 | text='Wisdom begins in wonder.',
38 | author='Socrates',
39 | )
40 |
--------------------------------------------------------------------------------
/tests/resources/ics_example.txt:
--------------------------------------------------------------------------------
1 | STATUS:CONFIRMED
2 | DTSTAMP:20210225T000000
3 | DTSTART;VALUE=DATE:20210225
4 | UID:feiertag2021-0323-3542-fcal.ch
5 | CATEGORIES:Public holidays
6 | END:VEVENT
7 | BEGIN:VEVENT
8 | SUMMARY:Purim
9 | DESCRIPTION:Feiertagskalender.ch - free data for private /internal use only.
10 | STATUS:CONFIRMED
11 | DTSTAMP:20210226T000000
12 | DTSTART;VALUE=DATE:20210226
13 | UID:feiertag2021-0324-3542-fcal.ch
14 | CATEGORIES:Public holidays
15 | END:VEVENT
16 | BEGIN:VEVENT
17 | SUMMARY:Pesach
18 | DESCRIPTION:Feiertagskalender.ch - free data for private /internal use only.
19 | STATUS:CONFIRMED
20 | DTSTAMP:20210328T000000
21 | DTSTART;VALUE=DATE:20210328
22 | UID:feiertag2021-0336-3542-fcal.ch
23 | CATEGORIES:Public holidays
24 | END:VEVENT
25 | BEGIN:VEVENT
26 | SUMMARY:Pesach
27 | DESCRIPTION:Feiertagskalender.ch - free data for private /internal use only.
28 | STATUS:CONFIRMED
29 | DTSTAMP:20210329T010000
30 | DTSTART;VALUE=DATE:20210329
31 | UID:feiertag2021-0336-3542-fcal.ch
32 | CATEGORIES:Public holidays
33 | END:VEVENT
34 | BEGIN:VEVENT
35 | SUMMARY:One More Holiday For Test
36 | DTSTAMP:20210101T010000
37 |
--------------------------------------------------------------------------------
/tests/resources/wrong_ics_example.txt:
--------------------------------------------------------------------------------
1 | STATUS:CONFIRMED
2 | DTSTAMP:20210225T000000
3 | DTSTART;VALUE=DATE:20210225
4 | UID:feiertag2021-0323-3542-fcal.ch
5 | CATEGORIES:Public holidays
6 | END:VEVENT
7 | BEGIN:VEVENT
8 | DESCRIPTION:Feiertagskalender.ch - free data for private /internal use only.
9 | STATUS:CONFIRMED
10 | DTSTAMP:20210226T000000
11 | DTSTART;VALUE=DATE:20210226
12 | UID:feiertag2021-0324-3542-fcal.ch
13 | CATEGORIES:Public holidays
14 | END:VEVENT
15 | BEGIN:VEVENT
16 | SUMMARY:Pesach
17 | DESCRIPTION:Feiertagskalender.ch - free data for private /internal use only.
18 | STATUS:CONFIRMED
19 | DTSTART;VALUE=DATE:20210328
20 | UID:feiertag2021-0336-3542-fcal.ch
21 | CATEGORIES:Public holidays
22 | END:VEVENT
23 | BEGIN:VEVENT
24 | SUMMARY:Pesach
25 | DESCRIPTION:Feiertagskalender.ch - free data for private /internal use only.
26 |
--------------------------------------------------------------------------------
/tests/security_testing_routes.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter, Depends, Request
2 |
3 | from app.internal.security.dependancies import (
4 | current_user, current_user_from_db,
5 | is_logged_in, is_manager, User
6 | )
7 |
8 |
9 | """
10 | These routes are for security testing.
11 | They represent an example for how to use
12 | security dependencies in other routes.
13 | """
14 | router = APIRouter(
15 | prefix="",
16 | tags=["/security"],
17 | responses={404: {"description": "Not found"}},
18 | )
19 |
20 |
21 | @router.get('/is_logged_in')
22 | async def is_logged_in(
23 | request: Request, user: bool = Depends(is_logged_in)):
24 | """This is how to protect route for logged in user only.
25 | Dependency will return True.
26 | if user not looged-in, will be redirected to login route.
27 | """
28 | return {"user": user}
29 |
30 |
31 | @router.get('/is_manager')
32 | async def is_manager(
33 | request: Request, user: bool = Depends(is_manager)):
34 | """This is how to protect route for logged in manager only.
35 | Dependency will return True.
36 | if user not looged-in, or have no manager permission,
37 | will be redirected to login route.
38 | """
39 | return {"manager": user}
40 |
41 |
42 | @router.get('/current_user_from_db')
43 | async def current_user_from_db(
44 | request: Request, user: User = Depends(current_user_from_db)):
45 | """This is how to protect route for logged in user only.
46 | Dependency will return User object.
47 | if user not looged-in, will be redirected to login route.
48 | """
49 | return {"user": user.username}
50 |
51 |
52 | @router.get('/current_user')
53 | async def current_user(
54 | request: Request, user: User = Depends(current_user)):
55 | """This is how to protect route for logged in user only.
56 | Dependency will return schema.CurrentUser object,
57 | contains user_id and username.
58 | if user not looged-in, will be redirected to login route.
59 | """
60 | return {"user": user.username}
61 |
--------------------------------------------------------------------------------
/tests/test_about.py:
--------------------------------------------------------------------------------
1 | def test_about_page(client):
2 | response = client.get("/about")
3 | assert response.ok
4 | assert b"Yam" in response.content
5 |
--------------------------------------------------------------------------------
/tests/test_agenda_internal.py:
--------------------------------------------------------------------------------
1 | from datetime import date, datetime
2 |
3 | import pytest
4 |
5 | from app.internal import agenda_events
6 | from app.internal.agenda_events import get_events_per_dates
7 |
8 |
9 | class TestAgenda:
10 | START = datetime(2021, 11, 1, 8, 00, 00)
11 | dates = [
12 | (START, datetime(2021, 11, 3, 8, 00, 0),
13 | '2 days'),
14 | (START, datetime(2021, 11, 3, 10, 30, 0),
15 | '2 days 2 hours and 30 minutes'),
16 | (START, datetime(2021, 11, 1, 8, 30, 0),
17 | '30 minutes'),
18 | (START, datetime(2021, 11, 1, 10, 00, 0),
19 | '2 hours'),
20 | (START, datetime(2021, 11, 1, 10, 30, 0),
21 | '2 hours and 30 minutes'),
22 | (START, datetime(2021, 11, 2, 10, 00, 0),
23 | 'a day and 2 hours'),
24 | ]
25 |
26 | @pytest.mark.parametrize('start, end, diff', dates)
27 | def test_get_time_delta_string(self, start, end, diff):
28 | assert agenda_events.get_time_delta_string(start, end) == diff
29 |
30 | def test_get_events_per_dates_success(self, today_event, session):
31 | events = get_events_per_dates(
32 | session=session,
33 | user_id=today_event.owner_id,
34 | start=today_event.start.date(),
35 | end=today_event.end.date(),
36 | )
37 | assert list(events) == [today_event]
38 |
39 | def test_get_events_per_dates_failure(self, yesterday_event, session):
40 | events = get_events_per_dates(
41 | session=session,
42 | user_id=yesterday_event.owner_id,
43 | start=date.today(),
44 | end=date.today(),
45 | )
46 | assert list(events) == []
47 |
--------------------------------------------------------------------------------
/tests/test_app.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.orm import Session
2 |
3 | from app.dependencies import get_db
4 |
5 |
6 | class TestApp:
7 |
8 | @staticmethod
9 | def test_get_db():
10 | assert isinstance(next(get_db()), Session)
11 |
--------------------------------------------------------------------------------
/tests/test_association.py:
--------------------------------------------------------------------------------
1 | class TestAssociation:
2 | def test_association_data(self, association, event):
3 | assert association.events == event
4 |
5 | def test_repr(self, association):
6 | assert (
7 | association.__repr__()
8 | == f'
')
10 |
--------------------------------------------------------------------------------
/tests/test_calendar_privacy.py:
--------------------------------------------------------------------------------
1 | from app.internal.calendar_privacy import can_show_calendar
2 | # TODO after user system is merged:
3 | # from app.internal.security.dependancies import CurrentUser
4 | from app.routers.user import create_user
5 |
6 |
7 | def test_can_show_calendar_public(session, user):
8 | user.privacy = "Public"
9 | # TODO to be replaced after user system is merged:
10 | # current_user = CurrentUser(**user.__dict__)
11 | current_user = user
12 | result = can_show_calendar(
13 | requested_user_username='test_username',
14 | db=session, current_user=current_user
15 | )
16 | assert result is True
17 | session.commit()
18 |
19 |
20 | def test_can_show_calendar_private(session, user):
21 | another_user = create_user(
22 | session=session,
23 | username='new_test_username2',
24 | email='new_test.email2@gmail.com',
25 | password='passpar_2',
26 | language_id=1
27 | )
28 | current_user = user
29 | # TODO to be replaced after user system is merged:
30 | # current_user = CurrentUser(**user.__dict__)
31 |
32 | result_a = can_show_calendar(
33 | requested_user_username='new_test_username2',
34 | db=session, current_user=current_user
35 | )
36 | result_b = can_show_calendar(
37 | requested_user_username='test_username',
38 | db=session, current_user=current_user
39 | )
40 | assert result_a is False
41 | assert result_b is True
42 | session.delete(another_user)
43 | session.commit()
44 |
--------------------------------------------------------------------------------
/tests/test_celebrity.py:
--------------------------------------------------------------------------------
1 | import datetime
2 |
3 | import pytest
4 |
5 | from app.internal.celebrity import get_today_month_and_day
6 |
7 | CELEBRITY_ROUTE = "/celebrity"
8 | FAKE_TIME = datetime.date(2018, 9, 18)
9 |
10 | BAD_DATES = [
11 | datetime.date(2021, 1, 1),
12 | datetime.date(1789, 7, 14),
13 | datetime.date(1776, 7, 4),
14 | datetime.date(1945, 1, 27),
15 | datetime.date(2000, 10, 16),
16 | ]
17 |
18 | GOOD_DATES = [
19 | datetime.date(2020, 9, 18),
20 | datetime.date(2019, 9, 18),
21 | datetime.date(2016, 9, 18),
22 | ]
23 |
24 |
25 | @pytest.fixture
26 | def datetime_mock(monkeypatch):
27 | class MockDateTime:
28 |
29 | @staticmethod
30 | def today():
31 | return FAKE_TIME
32 |
33 | monkeypatch.setattr(datetime, 'date', MockDateTime)
34 |
35 |
36 | @pytest.mark.parametrize('date', BAD_DATES)
37 | def test_get_today_month_and_day_bad(date, datetime_mock):
38 | assert get_today_month_and_day() != date.strftime("%m-%d")
39 |
40 |
41 | @pytest.mark.parametrize('date', GOOD_DATES)
42 | def test_get_today_month_and_day_good(date, datetime_mock):
43 | assert get_today_month_and_day() == date.strftime("%m-%d")
44 |
45 |
46 | def test_celebrity_page_exists(client):
47 | response = client.get(CELEBRITY_ROUTE)
48 | assert response.ok
49 | assert b'born today' in response.content
50 |
--------------------------------------------------------------------------------
/tests/test_comment.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from sqlalchemy.orm.session import Session
4 |
5 | from app.database.models import Comment, Event, User
6 | from app.internal import comment as cmt
7 | from app.internal.utils import delete_instance
8 |
9 |
10 | def test_create_comment(session: Session, event: Event, user: User) -> None:
11 | assert session.query(Comment).first() is None
12 | cmt.create_comment(session, event, 'test content')
13 | comment = session.query(Comment).first()
14 | assert comment
15 | delete_instance(session, comment)
16 |
17 |
18 | def test_parse_comment(session: Session, comment: Comment) -> None:
19 | data = {
20 | 'id': 1,
21 | 'avatar': 'profile.png',
22 | 'username': 'test_username',
23 | 'time': '01/01/2021 00:01',
24 | 'content': 'test comment',
25 | }
26 | assert cmt.parse_comment(session, comment) == data
27 |
28 |
29 | def test_display_comment(session: Session, event: Event,
30 | comment: Comment) -> None:
31 | comments = json.loads(cmt.display_comments(session, event))
32 | assert len(comments) == 1
33 |
34 |
35 | def test_display_comment_empty(session: Session, event: Event) -> None:
36 | comments = json.loads(cmt.display_comments(session, event))
37 | assert comments == []
38 |
39 |
40 | def test_delete_comment(session: Session, comment: Comment) -> None:
41 | assert session.query(Comment).first()
42 | assert cmt.delete_comment(session, comment.id)
43 | assert session.query(Comment).first() is None
44 | assert not cmt.delete_comment(session, comment.id)
45 |
--------------------------------------------------------------------------------
/tests/test_credits.py:
--------------------------------------------------------------------------------
1 | class TestCredits:
2 | CREDITS_OPENING = b"Say hello to our developers"
3 |
4 | @staticmethod
5 | def test_get_credits_ok_request(client):
6 | response = client.get("/credits")
7 | assert response.ok
8 | assert TestCredits.CREDITS_OPENING in response.content
9 |
--------------------------------------------------------------------------------
/tests/test_currency.py:
--------------------------------------------------------------------------------
1 | CURRENCY = '/currency'
2 | CUSTOM_DATE = "/2021-1-3"
3 |
4 |
5 | def test_router_good(client):
6 | resp = client.get(CURRENCY)
7 | assert resp.ok
8 | resp = client.get(CURRENCY + CUSTOM_DATE)
9 | assert resp.ok
10 | assert b'Currency' in resp.content
11 |
--------------------------------------------------------------------------------
/tests/test_friendview.py:
--------------------------------------------------------------------------------
1 | from fastapi import status
2 |
3 |
4 | class TestFriendview:
5 | FRIENDVIEW = "/friendview"
6 | NO_EVENTS = b"No mutual events found..."
7 |
8 | @staticmethod
9 | def test_friendview_page_no_arguments_when_no_today_events(
10 | friendview_test_client,
11 | session,
12 | ):
13 | resp = friendview_test_client.get(TestFriendview.FRIENDVIEW)
14 | assert resp.ok
15 | assert TestFriendview.NO_EVENTS in resp.content
16 |
17 | @staticmethod
18 | def test_no_show_events_user_2(friendview_test_client, today_event):
19 | resp = friendview_test_client.get(TestFriendview.FRIENDVIEW)
20 | assert resp.status_code == status.HTTP_200_OK
21 | assert b"event 7" not in resp.content
22 |
--------------------------------------------------------------------------------
/tests/test_holidays.py:
--------------------------------------------------------------------------------
1 | import os
2 | from app.database.models import Event, User
3 | from app.routers import profile
4 | from sqlalchemy.orm import Session
5 |
6 |
7 | class TestHolidaysImport:
8 | HOLIDAYS = '/profile/holidays/import'
9 |
10 | @staticmethod
11 | def test_import_holidays_page_exists(client):
12 | resp = client.get(TestHolidaysImport.HOLIDAYS)
13 | assert resp.ok
14 | assert b'Import holidays using ics file' in resp.content
15 |
16 | def test_get_holidays(self, session: Session, user: User):
17 | current_folder = os.path.dirname(os.path.realpath(__file__))
18 | resource_folder = os.path.join(current_folder, 'resources')
19 | test_file = os.path.join(resource_folder, 'ics_example.txt')
20 | with open(test_file) as file:
21 | ics_content = file.read()
22 | holidays = profile.get_holidays_from_file(ics_content, session)
23 | profile.save_holidays_to_db(holidays, session)
24 | assert len(session.query(Event).all()) == 4
25 |
26 | def test_wrong_file_get_holidays(self, session: Session, user: User):
27 | current_folder = os.path.dirname(os.path.realpath(__file__))
28 | resource_folder = os.path.join(current_folder, 'resources')
29 | test_file = os.path.join(resource_folder, 'wrong_ics_example.txt')
30 | with open(test_file) as file:
31 | ics_content = file.read()
32 | holidays = profile.get_holidays_from_file(ics_content, session)
33 | profile.save_holidays_to_db(holidays, session)
34 | assert len(session.query(Event).all()) == 0
35 |
--------------------------------------------------------------------------------
/tests/test_home.py:
--------------------------------------------------------------------------------
1 | class TestHome:
2 | URL = "/"
3 |
4 | @staticmethod
5 | def test_get_page(client):
6 | response = client.get(TestHome.URL)
7 | assert response.ok
8 |
--------------------------------------------------------------------------------
/tests/test_invitation.py:
--------------------------------------------------------------------------------
1 | from fastapi import status
2 |
3 | from app.routers.invitation import get_all_invitations, get_invitation_by_id
4 |
5 |
6 | class TestInvitations:
7 | NO_INVITATIONS = b"You don't have any invitations."
8 | URL = "/invitations/"
9 |
10 | @staticmethod
11 | def test_view_no_invitations(invitation_test_client):
12 | response = invitation_test_client.get(TestInvitations.URL)
13 | assert response.ok
14 | assert TestInvitations.NO_INVITATIONS in response.content
15 |
16 | @staticmethod
17 | def test_accept_invitations(user, invitation, invitation_test_client):
18 | invitation = {"invite_id ": invitation.id}
19 | resp = invitation_test_client.post(
20 | TestInvitations.URL, data=invitation)
21 | assert resp.status_code == status.HTTP_302_FOUND
22 |
23 | @staticmethod
24 | def test_get_all_invitations_success(invitation, event, user, session):
25 | invitations = get_all_invitations(event=event, db=session)
26 | assert invitations == [invitation]
27 | invitations = get_all_invitations(recipient=user, db=session)
28 | assert invitations == [invitation]
29 |
30 | @staticmethod
31 | def test_get_all_invitations_failure(user, session):
32 | invitations = get_all_invitations(unknown_parameter=user, db=session)
33 | assert invitations == []
34 |
35 | invitations = get_all_invitations(recipient=None, db=session)
36 | assert invitations == []
37 |
38 | @staticmethod
39 | def test_get_invitation_by_id(invitation, session):
40 | get_invitation = get_invitation_by_id(invitation.id, db=session)
41 | assert get_invitation == invitation
42 |
43 | @staticmethod
44 | def test_repr(invitation):
45 | invitation_repr = (
46 | f''
49 | )
50 | assert invitation.__repr__() == invitation_repr
51 |
--------------------------------------------------------------------------------
/tests/test_json_data_loader.py:
--------------------------------------------------------------------------------
1 | from app.database.models import Quote, Zodiac
2 | from app.internal import json_data_loader
3 |
4 |
5 | def get_objects_amount(session, table):
6 | return session.query(table).count()
7 |
8 |
9 | def test_load_daily_quotes(session):
10 | json_data_loader.load_to_database(session)
11 | assert get_objects_amount(session, Quote) > 0
12 |
13 |
14 | def test_load_zodiacs(session):
15 | json_data_loader.load_to_database(session)
16 | assert get_objects_amount(session, Zodiac) > 0
17 |
18 |
19 | # tests for basic functionality of the json data loader
20 | def test_load_data_with_json_value_error(mocker, session):
21 | mocker.patch('json.load', side_effect=ValueError)
22 | json_data_loader.load_to_database(session)
23 | assert get_objects_amount(session, Quote) == 0
24 |
25 |
26 | def test_data_not_load_twice_to_db(session):
27 | json_data_loader.load_to_database(session)
28 | first_quotes_amount = get_objects_amount(session, Quote)
29 | json_data_loader.load_to_database(session)
30 | assert first_quotes_amount == get_objects_amount(session, Quote)
31 |
--------------------------------------------------------------------------------
/tests/test_language.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import pytest
4 |
5 | from app.dependencies import templates
6 | from app.internal import languages
7 |
8 |
9 | class TestLanguage:
10 | # Empty, invalid, or valid, but unsupported language codes,
11 | # (currently 'en' and 'he') are set to the default language setting
12 | # at config.WEBSITE_LANGUAGE, which is currently set to 'en' (English).
13 | LANGUAGE_TESTS = [
14 | ('en', 'test python translation', True),
15 | ('he', 'בדיקת תרגום בפייתון', True),
16 | (None, 'test python translation', False),
17 | ('', 'test python translation', False),
18 | ('de', 'test python translation', False),
19 | (["en"], 'test python translation', False),
20 | (3, 'test python translation', False),
21 | ]
22 |
23 | NUMBER_OF_LANGUAGES = 2
24 |
25 | @staticmethod
26 | @pytest.mark.parametrize(
27 | "language_code, translation, is_valid", LANGUAGE_TESTS)
28 | def test_gettext_python(language_code, translation, is_valid):
29 | languages.set_ui_language(language_code)
30 |
31 | # i18n: String used in testing. Do not change.
32 | gettext_translation = _("test python translation")
33 | assert ((is_valid and gettext_translation == translation)
34 | or (not is_valid and gettext_translation == translation))
35 |
36 | @staticmethod
37 | @pytest.mark.parametrize(
38 | "language_code, translation, is_valid", LANGUAGE_TESTS)
39 | def test_gettext_html(language_code, translation, is_valid):
40 | languages.set_ui_language(language_code)
41 |
42 | template = templates.env.from_string(
43 | '{{ gettext("test python translation") }}')
44 | text = template.render()
45 | assert ((is_valid and translation in text)
46 | or (not is_valid and translation in text))
47 |
48 | @staticmethod
49 | def test_get_supported_languages():
50 | number_of_languages = len(list(languages._get_supported_languages()))
51 | assert number_of_languages == TestLanguage.NUMBER_OF_LANGUAGES
52 |
53 | @staticmethod
54 | def test_get_language_directory():
55 | pytest.MonkeyPatch().setattr(Path, 'is_dir', lambda x: True)
56 | assert languages._get_language_directory()
57 |
58 | @staticmethod
59 | def test_get_display_language():
60 | # TODO: Waiting for user registration.
61 | # Test: no user, user not logged in and user with non-english set.
62 | pass
63 |
--------------------------------------------------------------------------------
/tests/test_logger.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import pytest
4 |
5 | from app import config
6 | from app.internal.logger_customizer import LoggerConfigError, LoggerCustomizer
7 |
8 |
9 | class TestLogger:
10 | @staticmethod
11 | def test_log_debug(caplog, logger_instance):
12 | with caplog.at_level(logging.DEBUG):
13 | logger_instance.debug('Is it debugging now?')
14 | assert 'Is it debugging now?' in caplog.text
15 |
16 | @staticmethod
17 | def test_log_info(caplog, logger_instance):
18 | with caplog.at_level(logging.INFO):
19 | logger_instance.info('App started')
20 | assert 'App started' in caplog.text
21 |
22 | @staticmethod
23 | def test_log_error(caplog, logger_instance):
24 | with caplog.at_level(logging.ERROR):
25 | logger_instance.error('Something bad happened!')
26 | assert 'Something bad happened!' in caplog.text
27 |
28 | @staticmethod
29 | def test_log_critical(caplog, logger_instance):
30 | with caplog.at_level(logging.CRITICAL):
31 | logger_instance.critical("WE'RE DOOMED!")
32 | assert "WE'RE DOOMED!" in caplog.text
33 |
34 | @staticmethod
35 | def test_bad_configuration():
36 | with pytest.raises(LoggerConfigError):
37 | LoggerCustomizer.make_logger(config.LOG_PATH,
38 | config.LOG_FILENAME,
39 | 'eror',
40 | config.LOG_ROTATION_INTERVAL,
41 | config.LOG_RETENTION_INTERVAL,
42 | config.LOG_FORMAT)
43 |
--------------------------------------------------------------------------------
/tests/test_on_this_day_events.py:
--------------------------------------------------------------------------------
1 | from app.database.models import WikipediaEvents
2 | from app.internal.on_this_day_events import (get_on_this_day_events,
3 | insert_on_this_day_data)
4 |
5 |
6 | def test_insert_on_this_day_data(session):
7 | is_exists_data = session.query(WikipediaEvents).all()
8 | assert not is_exists_data
9 | insert_on_this_day_data(session)
10 | is_exists_data = session.query(WikipediaEvents).all()
11 | assert is_exists_data is not None
12 |
13 |
14 | def test_get_on_this_day_events(session):
15 | data = get_on_this_day_events(session)
16 | assert isinstance(data, dict)
17 | assert isinstance(data.get('events'), list)
18 | assert isinstance(data.get('wikipedia'), str)
19 |
20 |
21 | def test_get_on_this_day_events_exists(session):
22 | fake_object = WikipediaEvents(
23 | events=['fake'], wikipedia="www.fake.com", date_="not a date string")
24 | session.add(fake_object)
25 | session.commit()
26 | fake_data = get_on_this_day_events(session)
27 | assert fake_data.events[0] == 'fake'
28 | assert fake_data.wikipedia == 'www.fake.com'
29 | assert fake_data.date_ == 'not a date string'
30 |
--------------------------------------------------------------------------------
/tests/test_psql_environment.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from app.database import create_env_engine
4 | from app.database.models import PSQLEnvironmentError
5 | from app.main import create_tables
6 |
7 |
8 | def test_main_create_tables_error(sqlite_engine):
9 | raised_error = False
10 | with pytest.raises(PSQLEnvironmentError):
11 | create_tables(sqlite_engine, True)
12 | raised_error = True
13 | assert raised_error
14 |
15 |
16 | def test_database_create_engine():
17 | sqlalchemy_database_url = "postgresql://postgres:1234@localhost/postgres"
18 | engine = create_env_engine(True, sqlalchemy_database_url)
19 | assert 'postgres' in str(engine.url)
20 | sqlalchemy_database_url = "sqlite:///./test1.db"
21 | engine = create_env_engine(False, sqlalchemy_database_url)
22 | assert 'sqlite' in str(engine.url)
23 |
--------------------------------------------------------------------------------
/tests/test_quotes.py:
--------------------------------------------------------------------------------
1 | from datetime import date
2 |
3 | from app.internal import daily_quotes
4 |
5 | DATE = date(2021, 1, 1)
6 | DATE2 = date(2021, 1, 2)
7 |
8 |
9 | def test_get_quote():
10 | quotes_fields = {
11 | 'text': 'some_quote',
12 | 'author': 'Freud',
13 | }
14 | result = daily_quotes.get_quote(quotes_fields)
15 | assert result.text == 'some_quote'
16 | assert result.author == 'Freud'
17 |
18 |
19 | def test_get_quote_of_day_no_quotes(session):
20 | assert daily_quotes.get_quote_of_day(session, DATE) is None
21 |
22 |
23 | def test_get_quote_of_day_get_first_quote(session, quote1, quote2):
24 | assert daily_quotes.get_quote_of_day(
25 | session, DATE).text == quote1.text
26 |
27 |
28 | def test_get_quote_of_day_get_second_quote(session, quote1, quote2):
29 | assert daily_quotes.get_quote_of_day(
30 | session, DATE2).text == quote2.text
31 |
--------------------------------------------------------------------------------
/tests/test_share_event.py:
--------------------------------------------------------------------------------
1 | from app.routers.invitation import get_all_invitations
2 | from app.routers.share import (accept, send_email_invitation,
3 | send_in_app_invitation, share, sort_emails)
4 |
5 |
6 | class TestShareEvent:
7 |
8 | def test_share_success(self, user, event, session):
9 | participants = [user.email]
10 | share(event, participants, session)
11 | invitations = get_all_invitations(db=session, recipient_id=user.id)
12 | assert invitations != []
13 |
14 | def test_share_failure(self, event, session):
15 | participants = [event.owner.email]
16 | share(event, participants, session)
17 | invitations = get_all_invitations(
18 | db=session, recipient_id=event.owner.id)
19 | assert invitations == []
20 |
21 | def test_sort_emails(self, user, session):
22 | # the user is being imported
23 | # so he will be created
24 | data = [
25 | 'test.email@gmail.com', # registered user
26 | 'not_logged_in@gmail.com', # unregistered user
27 | ]
28 | sorted_data = sort_emails(data, session=session)
29 | assert sorted_data == {
30 | 'registered': ['test.email@gmail.com'],
31 | 'unregistered': ['not_logged_in@gmail.com']
32 | }
33 |
34 | def test_send_in_app_invitation_success(
35 | self, user, sender, event, session
36 | ):
37 | assert send_in_app_invitation([user.email], event, session=session)
38 | invitation = get_all_invitations(db=session, recipient=user)[0]
39 | assert invitation.event.owner == sender
40 | assert invitation.recipient == user
41 | session.delete(invitation)
42 |
43 | def test_send_in_app_invitation_failure(
44 | self, user, sender, event, session):
45 | assert (send_in_app_invitation(
46 | [sender.email], event, session=session) is False)
47 |
48 | def test_send_email_invitation(self, user, event):
49 | send_email_invitation([user.email], event)
50 | # TODO add email tests
51 | assert True
52 |
53 | def test_accept(self, invitation, session):
54 | accept(invitation, session=session)
55 | assert invitation.status == 'accepted'
56 |
--------------------------------------------------------------------------------
/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.orm import Session
2 |
3 | from app.database.models import User
4 | from app.internal import utils
5 |
6 |
7 | class TestUtils:
8 |
9 | def test_save_success(self, user: User, session: Session) -> None:
10 | user.username = 'edit_username'
11 | assert utils.save(session, user)
12 |
13 | def test_save_failure(self, session: Session) -> None:
14 | user = 'not a user instance'
15 | assert not utils.save(session, user)
16 |
17 | def test_create_model(self, session: Session) -> None:
18 | assert session.query(User).first() is None
19 | info = {
20 | 'username': 'test',
21 | 'email': 'test@test.com',
22 | 'password': 'test1234'
23 | }
24 | utils.create_model(session, User, **info)
25 | assert session.query(User).first()
26 |
27 | def test_delete_instance(self, session: Session, user: User):
28 | assert session.query(User).first()
29 | utils.delete_instance(session, user)
30 | assert session.query(User).first() is None
31 |
32 | def test_get_current_user(self, session: Session) -> None:
33 | # Code revision required after user login feature is added
34 | assert session.query(User).filter_by(id=1).first() is None
35 | utils.get_current_user(session)
36 | assert session.query(User).filter_by(id=1).first()
37 |
38 | def test_get_user(self, user: User, session: Session) -> None:
39 | assert utils.get_user(session, user.id) == user
40 | assert utils.get_user(session, 2) is None
41 |
--------------------------------------------------------------------------------
/tests/test_weekview.py:
--------------------------------------------------------------------------------
1 | from bs4 import BeautifulSoup
2 | import pytest
3 |
4 | from app.routers.event import create_event
5 | from app.routers.weekview import get_week_dates
6 |
7 |
8 | def create_weekview_event(events, session, user):
9 | for event in events:
10 | create_event(
11 | db=session,
12 | title='test',
13 | start=event.start,
14 | end=event.end,
15 | owner_id=user.id,
16 | color=event.color
17 | )
18 |
19 |
20 | def test_get_week_dates(weekdays, sunday):
21 | week_dates = list(get_week_dates(sunday))
22 | for i in range(6):
23 | assert week_dates[i].strftime('%A') == weekdays[i]
24 |
25 |
26 | def test_weekview_day_names(session, user, client, weekdays):
27 | response = client.get("/week/2021-1-3")
28 | soup = BeautifulSoup(response.content, 'html.parser')
29 | day_divs = soup.find_all("div", {"class": 'day-name'})
30 | for i in range(6):
31 | assert weekdays[i][:3].upper() in str(day_divs[i])
32 |
33 |
34 | def test_weekview_day_dates(session, user, client, sunday):
35 | response = client.get("/week/2021-1-3")
36 | soup = BeautifulSoup(response.content, 'html.parser')
37 | day_divs = soup.find_all("span", {"class": 'date-nums'})
38 | week_dates = list(get_week_dates(sunday))
39 | for i in range(6):
40 | time_str = f'{week_dates[i].day} / {week_dates[i].month}'
41 | assert time_str in day_divs[i]
42 |
43 |
44 | @pytest.mark.parametrize(
45 | "date,event",
46 | [("2021-1-31", 'event1'),
47 | ("2021-1-31", 'event2'),
48 | ("2021-2-3", 'event3')]
49 | )
50 | def test_weekview_html_events(
51 | event1, event2, event3, session, user, client, date, event
52 | ):
53 | create_weekview_event([event1, event2, event3], session=session, user=user)
54 | response = client.get(f"/week/{date}")
55 | soup = BeautifulSoup(response.content, 'html.parser')
56 | assert event in str(soup.find("div", {"id": event}))
57 |
--------------------------------------------------------------------------------
/tests/test_whatsapp.py:
--------------------------------------------------------------------------------
1 | from app.routers import whatsapp
2 |
3 |
4 | def test_whatsapp_send():
5 | """Test with a valid phone number and text.
6 |
7 | Redirects directly to the specified contact and the message will
8 | already be there (or to WhatsApp Web if the call is from the web).
9 |
10 | """
11 | phone_number = "972536106106"
12 | message = 'Hello hello'
13 | expected = ("https://api.whatsapp.com/send?phone=972536106106&text="
14 | "Hello+hello")
15 | assert whatsapp.make_link(phone_number, message) == expected
16 |
17 |
18 | def test_wrong_phone_number():
19 | """Text with invalid phone number and valid text.
20 |
21 | Redirects you to a popup: The phone number shared via a link is incorrect.
22 |
23 | """
24 | phone_number = "999999"
25 | message = 'Hello hello'
26 | expected = "https://api.whatsapp.com/send?phone=999999&text=Hello+hello"
27 | assert whatsapp.make_link(phone_number, message) == expected
28 |
29 |
30 | def test_no_message():
31 | """Test with valid phone number and no text.
32 |
33 | Redirects to WhatsApp of the specified number. Write your own message.
34 |
35 | """
36 | phone_number = "972536106106"
37 | message = ''
38 | expected = "https://api.whatsapp.com/send?phone=972536106106&text="
39 | assert whatsapp.make_link(phone_number, message) == expected
40 |
41 |
42 | def test_no_number():
43 | """Test with no phone number and valid text.
44 |
45 | Redirects to WhatsApp window. Choose someone from your own contact list.
46 |
47 | """
48 | phone_number = ""
49 | message = 'Hello hello'
50 | expected = "https://api.whatsapp.com/send?phone=&text=Hello+hello"
51 | assert whatsapp.make_link(phone_number, message) == expected
52 |
53 |
54 | def test_end_to_end_testing(client):
55 | resp = client.get('/whatsapp?phone_number=972536106106&message=testing')
56 | assert resp.ok
57 | assert resp.json
58 |
--------------------------------------------------------------------------------
/tests/test_zodiac.py:
--------------------------------------------------------------------------------
1 | from datetime import date
2 |
3 | from app.internal import zodiac
4 |
5 | DATE = date(2021, 3, 22)
6 | DATE2 = date(2021, 4, 10)
7 |
8 |
9 | def test_create_zodiac_object():
10 | zodiac_fields = {
11 | 'name': 'aries',
12 | 'start_month': 3,
13 | 'start_day_in_month': 20,
14 | 'end_month': 4,
15 | 'end_day_in_month': 19,
16 | }
17 |
18 | result = zodiac.get_zodiac(zodiac_fields)
19 | assert result.name == 'aries'
20 | assert result.start_month == 3
21 | assert str(result) == ""
22 |
23 |
24 | def test_get_correct_zodiac_first_half_month(session, zodiac_sign):
25 | result = zodiac.get_zodiac_of_day(session, DATE)
26 | assert result.name == zodiac_sign.name
27 |
28 |
29 | def test_get_correct_zodiac_second_half_month(session, zodiac_sign):
30 | result = zodiac.get_zodiac_of_day(session, DATE2)
31 | assert result.name == zodiac_sign.name
32 |
33 |
34 | def test_get_correct_month_zodiac(session, zodiac_sign):
35 | result = zodiac.get_zodiac_of_month(session, DATE2)
36 | assert result.name == zodiac_sign.name
37 |
38 |
39 | def test_no_zodiac(session):
40 | result = zodiac.get_zodiac_of_month(session, DATE)
41 | assert result is None
42 |
--------------------------------------------------------------------------------
/tests/user_fixture.py:
--------------------------------------------------------------------------------
1 | from collections import Generator
2 |
3 | import pytest
4 | from sqlalchemy.orm import Session
5 |
6 | from app.database.models import User
7 | from app.internal.utils import create_model, delete_instance
8 |
9 |
10 | @pytest.fixture
11 | def user(session: Session) -> Generator[User, None, None]:
12 | mock_user = create_model(
13 | session, User,
14 | username='test_username',
15 | password='test_password',
16 | email='test.email@gmail.com',
17 | language_id=1,
18 | )
19 | yield mock_user
20 | delete_instance(session, mock_user)
21 |
22 |
23 | @pytest.fixture
24 | def sender(session: Session) -> Generator[User, None, None]:
25 | mock_user = create_model(
26 | session, User,
27 | username='sender_username',
28 | password='sender_password',
29 | email='sender.email@gmail.com',
30 | language_id=1,
31 | )
32 | yield mock_user
33 | delete_instance(session, mock_user)
34 |
--------------------------------------------------------------------------------
/tests/utils.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.orm import Session
2 |
3 |
4 | def create_model(session: Session, model_class, **kw):
5 | instance = model_class(**kw)
6 | session.add(instance)
7 | session.commit()
8 | return instance
9 |
10 |
11 | def delete_instance(session: Session, instance):
12 | session.delete(instance)
13 | session.commit()
14 |
--------------------------------------------------------------------------------
/tests/zodiac_fixture.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from sqlalchemy.orm import Session
3 |
4 | from app.database.models import Zodiac
5 | from app.internal.utils import create_model, delete_instance
6 |
7 |
8 | @pytest.fixture
9 | def zodiac_sign(session: Session) -> Zodiac:
10 | zodiac = create_model(
11 | session, Zodiac,
12 | name="aries",
13 | start_month=3,
14 | start_day_in_month=20,
15 | end_month=4,
16 | end_day_in_month=19,
17 | )
18 | yield zodiac
19 | delete_instance(session, zodiac)
20 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | skipsdist=True
3 | envlist = cov, flake8
4 |
5 | [testenv]
6 | basepython = python3
7 | deps = -rrequirements.txt
8 |
9 |
10 | [testenv:rep]
11 | commands =
12 | pytest --cov=app --cov-report=html
13 |
14 |
15 | [testenv:cov]
16 | commands =
17 | pytest --cov=app
18 |
19 |
20 | [testenv:flake8]
21 | deps = flake8
22 | commands =
23 | flake8 app
24 | flake8 tests
25 |
26 |
27 | # pytest Configuration
28 | [pytest]
29 | junit_family = xunit2
30 | testpaths = tests
31 | filterwarnings =
32 | ignore:.*'collections'.*'collections.abc'.*:DeprecationWarning
33 | ignore:Task.all_tasks() is deprecated, use asyncio.all_tasks().*:PendingDeprecationWarning
34 |
35 | # Flake8 Configuration
36 | [flake8]
37 | # gettext() adds _() to the global namespace. This lets flake recognize it.
38 | builtins =
39 | _,
40 |
--------------------------------------------------------------------------------