From 12cc75c4cb22582646f58b04ec033f2275e2477a Mon Sep 17 00:00:00 2001
From: leddest <46251307+leddest@users.noreply.github.com>
Date: Wed, 20 Jan 2021 19:00:48 +0200
Subject: [PATCH 01/16] Minor changes
---
app/main.py | 2 +-
app/routers/profile.py | 11 -----------
tests/test_profile.py | 1 +
3 files changed, 2 insertions(+), 12 deletions(-)
diff --git a/app/main.py b/app/main.py
index 3af7bee2..b48bdae5 100644
--- a/app/main.py
+++ b/app/main.py
@@ -5,7 +5,7 @@
from app.database.database import engine
from app.dependencies import (
MEDIA_PATH, STATIC_PATH, templates)
-from .routers import agenda, event, profile
+from app.routers import agenda, event, profile
models.Base.metadata.create_all(bind=engine)
diff --git a/app/routers/profile.py b/app/routers/profile.py
index 39724939..3d73a481 100644
--- a/app/routers/profile.py
+++ b/app/routers/profile.py
@@ -44,8 +44,6 @@ async def profile(
session.commit()
user = session.query(User).filter_by(id=1).first()
- session.close()
-
return templates.TemplateResponse("profile.html", {
"request": request,
"user": user,
@@ -65,8 +63,6 @@ async def update_user_fullname(
user.full_name = new_fullname
session.commit()
- session.close()
-
url = router.url_path_for("profile")
response = RedirectResponse(url=url, status_code=HTTP_302_FOUND)
return response
@@ -84,8 +80,6 @@ async def update_user_email(
user.email = new_email
session.commit()
- session.close()
-
url = router.url_path_for("profile")
return RedirectResponse(url=url, status_code=HTTP_302_FOUND)
@@ -102,8 +96,6 @@ async def update_profile(
user.description = new_description
session.commit()
- session.close()
-
url = router.url_path_for("profile")
return RedirectResponse(url=url, status_code=HTTP_302_FOUND)
@@ -119,10 +111,7 @@ async def upload_user_photo(
# Save to database
user.avatar = await process_image(pic, user)
session.commit()
-
finally:
- session.close()
-
url = router.url_path_for("profile")
return RedirectResponse(url=url, status_code=HTTP_302_FOUND)
diff --git a/tests/test_profile.py b/tests/test_profile.py
index 4fbb33b9..4385bc1a 100644
--- a/tests/test_profile.py
+++ b/tests/test_profile.py
@@ -107,3 +107,4 @@ def test_upload_user_photo(profile_test_client):
# Validate new picture size
new_avatar_path = os.path.join(MEDIA_PATH, 'fake_user.png')
assert Image.open(new_avatar_path).size == config.AVATAR_SIZE
+ os.remove(new_avatar_path)
From a7793c355a0bc8202eb0dff3654fe57dd9d1f99d Mon Sep 17 00:00:00 2001
From: leddest <46251307+leddest@users.noreply.github.com>
Date: Thu, 21 Jan 2021 18:00:39 +0200
Subject: [PATCH 02/16] Add telegram client
---
app/config.py.example | 4 ++
app/database/models.py | 1 +
app/main.py | 8 ++-
app/routers/profile.py | 19 +++++-
app/routers/telegram.py | 41 ++++++++++++
app/telegram/__init__.py | 0
app/telegram/handlers.py | 55 ++++++++++++++++
app/telegram/keyboards.py | 0
app/telegram/models.py | 40 +++++++++++
app/telegram/pylander.py | 16 +++++
app/templates/profile.html | 50 ++++++++++++--
requirements.txt | 2 +
tests/conftest.py | 3 +-
tests/test_profile.py | 17 +++++
tests/test_telegram.py | 131 +++++++++++++++++++++++++++++++++++++
15 files changed, 376 insertions(+), 11 deletions(-)
create mode 100644 app/routers/telegram.py
create mode 100644 app/telegram/__init__.py
create mode 100644 app/telegram/handlers.py
create mode 100644 app/telegram/keyboards.py
create mode 100644 app/telegram/models.py
create mode 100644 app/telegram/pylander.py
create mode 100644 tests/test_telegram.py
diff --git a/app/config.py.example b/app/config.py.example
index b4c8ccf2..21a5dfc6 100644
--- a/app/config.py.example
+++ b/app/config.py.example
@@ -8,3 +8,7 @@ DEVELOPMENT_DATABASE_STRING = "sqlite:///./dev.db"
MEDIA_DIRECTORY = 'media'
PICTURE_EXTENSION = '.png'
AVATAR_SIZE = (120, 120)
+
+# TELEGRAM
+BOT_API = "paste bot-api"
+WEBHOOK_URL = "use real webhook url or generate webhook for your local host with Ngrok"
diff --git a/app/database/models.py b/app/database/models.py
index 0c92ae94..310dd288 100644
--- a/app/database/models.py
+++ b/app/database/models.py
@@ -14,6 +14,7 @@ class User(Base):
full_name = Column(String)
description = Column(String, default="Happy new user!")
avatar = Column(String, default="profile.png")
+ telegram_id = Column(String, unique=True)
is_active = Column(Boolean, default=True)
diff --git a/app/main.py b/app/main.py
index b48bdae5..545216c6 100644
--- a/app/main.py
+++ b/app/main.py
@@ -1,11 +1,14 @@
from fastapi import FastAPI, Request
+from starlette.responses import RedirectResponse
+from starlette.status import HTTP_302_FOUND
from fastapi.staticfiles import StaticFiles
from app.database import models
from app.database.database import engine
from app.dependencies import (
MEDIA_PATH, STATIC_PATH, templates)
-from app.routers import agenda, event, profile
+from app.telegram.pylander import pylander
+from app.routers import agenda, event, profile, telegram
models.Base.metadata.create_all(bind=engine)
@@ -17,6 +20,9 @@
app.include_router(profile.router)
app.include_router(event.router)
app.include_router(agenda.router)
+app.include_router(telegram.router)
+
+pylander.set_webhook()
@app.get("/")
diff --git a/app/routers/profile.py b/app/routers/profile.py
index 3d73a481..901bc227 100644
--- a/app/routers/profile.py
+++ b/app/routers/profile.py
@@ -64,8 +64,7 @@ async def update_user_fullname(
session.commit()
url = router.url_path_for("profile")
- response = RedirectResponse(url=url, status_code=HTTP_302_FOUND)
- return response
+ return RedirectResponse(url=url, status_code=HTTP_302_FOUND)
@router.post("/update_user_email")
@@ -116,6 +115,22 @@ async def upload_user_photo(
return RedirectResponse(url=url, status_code=HTTP_302_FOUND)
+@router.post("/update_telegram_id")
+async def update_telegram_id(
+ request: Request, session=Depends(get_db)):
+
+ user = session.query(User).filter_by(id=1).first()
+ data = await request.form()
+ new_telegram_id = data['telegram_id']
+
+ # Update database
+ user.telegram_id = new_telegram_id
+ session.commit()
+
+ url = router.url_path_for("profile")
+ return RedirectResponse(url=url, status_code=HTTP_302_FOUND)
+
+
async def process_image(image, user):
img = Image.open(io.BytesIO(image))
width, height = img.size
diff --git a/app/routers/telegram.py b/app/routers/telegram.py
new file mode 100644
index 00000000..b00e1c80
--- /dev/null
+++ b/app/routers/telegram.py
@@ -0,0 +1,41 @@
+from fastapi import APIRouter, Depends, Request
+import json
+import requests
+from starlette.responses import RedirectResponse
+from starlette.status import HTTP_302_FOUND
+
+from app.database.database import get_db
+from app.database.models import User
+from app.telegram.handlers import MessageHandler, reply_unknown_user
+from app.telegram.models import Chat
+
+
+router = APIRouter(
+ prefix="/telegram",
+ tags=["telegram"],
+ responses={404: {"description": "Not found"}},
+)
+
+
+@router.get("/")
+async def telegram(request: Request, session=Depends(get_db)):
+
+ # todo: Add templating
+ return "Start using PyLander telegram bot!"
+
+
+@router.post("/")
+async def bot_client(request: Request, session=Depends(get_db)):
+ req = await request.json()
+ chat = Chat(req)
+ message = MessageHandler(chat)
+
+ # Check if current chatter in DB
+ user = session.query(User).filter_by(telegram_id=chat.user_id).first()
+
+ if user is None:
+ return await reply_unknown_user(chat)
+ else:
+ # Process reply
+ message.process_callback()
+ return user
diff --git a/app/telegram/__init__.py b/app/telegram/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/app/telegram/handlers.py b/app/telegram/handlers.py
new file mode 100644
index 00000000..042066c7
--- /dev/null
+++ b/app/telegram/handlers.py
@@ -0,0 +1,55 @@
+from .models import Chat
+from .pylander import pylander
+
+
+class MessageHandler:
+ COMMANDS = ['/start', '/logout', '/show_events', '/new_event']
+
+ def __init__(self, chat: Chat):
+ self.chat = chat
+ self.handlers = {}
+ for method in dir(self):
+ if method.endswith('_handler'):
+ method_name = method[:-len('_handler')]
+ self.handlers[method_name] = getattr(MessageHandler, method)
+
+ def process_callback(self):
+ if self.chat.message in self.COMMANDS:
+ return self.handlers[self.chat.message[1:]](self)
+ elif (self.chat.message in self.handlers
+ and f'/{self.chat.message}' not in self.COMMANDS):
+ return self.handlers[self.chat.message](self)
+ else:
+ return self.default_handler()
+
+ def default_handler(self):
+ answer = "Unknown command"
+ pylander.send_message(chat_id=self.chat.user_id, text=answer)
+ return answer
+
+ def start_handler(self):
+ answer = f'''Hello, {self.chat.first_name}!
+Welcome to Pylander telegram client!'''
+ pylander.send_message(chat_id=self.chat.user_id, text=answer)
+ return answer
+
+ def show_events_handler(self):
+ answer = 'Choose events day'
+ pylander.send_message(chat_id=self.chat.user_id, text=answer)
+ return answer
+
+
+async def reply_unknown_user(chat):
+ answer = f'''
+Hello, {chat.first_name}!
+
+To use PyLander Bot you have to register
+your Telegram Id in your profile page.
+
+Your Id is {chat.user_id}
+Keep it secret!
+
+https://calendar.pythonic.guru/profile/
+'''
+ pylander.send_message(chat_id=chat.user_id, text=answer)
+ return answer
diff --git a/app/telegram/keyboards.py b/app/telegram/keyboards.py
new file mode 100644
index 00000000..e69de29b
diff --git a/app/telegram/models.py b/app/telegram/models.py
new file mode 100644
index 00000000..83d65412
--- /dev/null
+++ b/app/telegram/models.py
@@ -0,0 +1,40 @@
+import requests
+
+
+class Chat:
+ def __init__(self, message):
+ self.message = message['message']['text']
+ self.user_id = message['message']['from']['id']
+ self.username = message['message']['from']['username']
+ self.first_name = message['message']['from']['first_name']
+
+
+class Bot:
+ def __init__(self, bot_api, webhook_url):
+ self.base = self._set_base_url(bot_api)
+ self.webhook_setter_url = self._set_webhook_setter_url(webhook_url)
+
+ def _set_base_url(self, bot_api):
+ return f'https://api.telegram.org/bot{bot_api}/'
+
+ def _set_webhook_setter_url(self, webhook_url):
+ return f'{self.base}setWebhook?url={webhook_url}/telegram/'
+
+ def set_webhook(self):
+ return requests.get(self.webhook_setter_url)
+
+ def drop_webhook(self):
+ url = f'{self.base}deleteWebhook'
+ data = {
+ 'drop_pending_updates': True
+ }
+ return requests.get(url, data=data)
+
+ def send_message(self, chat_id, text, reply_markup=[]):
+ message = {
+ 'chat_id': chat_id,
+ 'text': text,
+ 'reply_markup': reply_markup
+ }
+ url = f'{self.base}sendMessage'
+ return requests.post(url, data=message)
diff --git a/app/telegram/pylander.py b/app/telegram/pylander.py
new file mode 100644
index 00000000..89859c62
--- /dev/null
+++ b/app/telegram/pylander.py
@@ -0,0 +1,16 @@
+import os
+
+import os
+
+from dotenv import load_dotenv
+
+from app import config
+from .models import Bot
+
+load_dotenv()
+
+
+BOT_API = os.getenv("BOT_API", config.BOT_API)
+WEBHOOK_URL = os.getenv("WEBHOOK_URL", config.WEBHOOK_URL)
+
+pylander = Bot(BOT_API, WEBHOOK_URL)
diff --git a/app/templates/profile.html b/app/templates/profile.html
index 9a0ddbda..0f42a5a1 100644
--- a/app/templates/profile.html
+++ b/app/templates/profile.html
@@ -21,25 +21,31 @@
+
+
+
@@ -131,6 +137,32 @@ Upload photo
+
+
+
@@ -158,14 +190,18 @@ {{ user.full_name }}
- Features
+ Explore more features
+ {% if not user.telegram_id %}
-
- Export my calendar
+
+ Try PyLander bot
+
+ {% endif %}
-
- Your feature
+ Export my calendar
-
Your feature
diff --git a/requirements.txt b/requirements.txt
index 9398c131..bdcb43c4 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,6 @@
aiofiles==0.6.0
arrow==0.17.0
+asyncio==3.4.3
atomicwrites==1.4.0
attrs==20.3.0
certifi==2020.12.5
@@ -25,6 +26,7 @@ py==1.10.0
pydantic==1.7.3
pyparsing==2.4.7
pytest==6.2.1
+pytest-asyncio==0.14.0
pytest-cov==2.10.1
python-dateutil==2.8.1
python-dotenv==0.15.0
diff --git a/tests/conftest.py b/tests/conftest.py
index 9960301b..12f0aa7d 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -41,7 +41,8 @@ def get_test_placeholder_user():
username='fake_user',
email='fake@mail.fake',
password='123456fake',
- full_name='FakeName'
+ full_name='FakeName',
+ telegram_id=''
)
diff --git a/tests/test_profile.py b/tests/test_profile.py
index 4385bc1a..939705db 100644
--- a/tests/test_profile.py
+++ b/tests/test_profile.py
@@ -88,6 +88,23 @@ def test_update_user_description(profile_test_client):
assert b"FastAPI Developer" in data
+def test_update_telegram_id(profile_test_client):
+ new_telegram_id = {
+ 'telegram_id': "12345"
+ }
+ # Get profile page and initialize database
+ profile = profile_test_client.get('/profile')
+
+ # Post new data
+ profile = profile_test_client.post(
+ '/profile/update_telegram_id', data=new_telegram_id)
+ assert profile.status_code == 302
+
+ # Get updated data
+ data = profile_test_client.get('/profile').content
+ assert b"12345" in data
+
+
def test_upload_user_photo(profile_test_client):
example_new_photo = f"{MEDIA_PATH}/example.png"
diff --git a/tests/test_telegram.py b/tests/test_telegram.py
new file mode 100644
index 00000000..5d5a47e3
--- /dev/null
+++ b/tests/test_telegram.py
@@ -0,0 +1,131 @@
+import asyncio
+import pytest
+
+from app import config
+from app.telegram.handlers import MessageHandler, reply_unknown_user
+from app.telegram.models import Bot, Chat
+
+
+def gen_message(text):
+ return {
+ 'update_id': 10000000,
+ 'message': {
+ 'message_id': 2434,
+ 'from': {
+ 'id': 666666,
+ 'is_bot': False,
+ 'first_name': 'Moshe',
+ 'username': 'banana',
+ 'language_code': 'en'
+ },
+ 'chat': {
+ 'id': 666666,
+ 'first_name': 'Moshe',
+ 'username': 'banana',
+ 'type': 'private'
+ },
+ 'date': 1611240725,
+ 'text': f'{text}'
+ }
+ }
+
+
+def test_chat_model():
+ chat = Chat(gen_message('Cool message'))
+ assert chat.message == 'Cool message'
+ assert chat.user_id == 666666
+ assert chat.username == 'banana'
+ assert chat.first_name == 'Moshe'
+
+
+def test_bot_model():
+ bot = Bot("fake bot id", "https://google.com")
+ assert bot.base == 'https://api.telegram.org/botfake bot id/'
+ assert bot.webhook_setter_url == f'https://api.telegram.org/botfake \
+bot id/setWebhook?url=https://google.com/telegram/'
+
+ assert bot.base == bot._set_base_url("fake bot id")
+ assert bot.webhook_setter_url == bot._set_webhook_setter_url(
+ "https://google.com")
+
+ set_request = bot.set_webhook()
+ assert set_request.status_code == 404
+ assert set_request.json() == {
+ 'ok': False,
+ 'error_code': 404,
+ 'description': 'Not Found'
+ }
+
+ drop_request = bot.drop_webhook()
+ assert drop_request.status_code == 404
+ assert drop_request.json() == {
+ 'ok': False,
+ 'error_code': 404,
+ 'description': 'Not Found'
+ }
+
+ send_request = bot.send_message("654654645", "hello")
+ assert send_request.status_code == 404
+ assert send_request.json() == {
+ 'ok': False,
+ 'error_code': 404,
+ 'description': 'Not Found'
+ }
+
+
+def test_start_handlers():
+ chat = Chat(gen_message('/start'))
+ message = MessageHandler(chat)
+
+ assert 'start' in message.handlers
+ assert 'default' in message.handlers
+ assert 'show_events' in message.handlers
+ assert message.process_callback() == f'''Hello, {message.chat.first_name}!
+Welcome to Pylander telegram client!'''
+
+
+def test_default_handlers():
+ wrong_start = MessageHandler(Chat(gen_message('start')))
+ wrong_show_events = MessageHandler(Chat(gen_message('start')))
+ message = MessageHandler(Chat(gen_message('hello')))
+
+ assert wrong_start.process_callback() == "Unknown command"
+ assert wrong_show_events.process_callback() == "Unknown command"
+ assert message.process_callback() == "Unknown command"
+
+
+def test_show_events_handler():
+ chat = Chat(gen_message('/show_events'))
+ message = MessageHandler(chat)
+ assert message.process_callback() == 'Choose events day'
+
+
+@pytest.mark.asyncio
+async def test_reply_unknown_user():
+ chat = Chat(gen_message('/show_events'))
+ answer = await reply_unknown_user(chat)
+ assert answer == '''
+Hello, Moshe!
+
+To use PyLander Bot you have to register
+your Telegram Id in your profile page.
+
+Your Id is 666666
+Keep it secret!
+
+https://calendar.pythonic.guru/profile/
+'''
+
+
+def test_telegram_router(profile_test_client):
+ page = profile_test_client.get('/telegram')
+ data = page.content
+ assert page.status_code == 200
+ assert b"Start using PyLander telegram bot!" in data
+
+
+def test_bot_client(profile_test_client):
+
+ page = profile_test_client.post(
+ '/telegram', data=gen_message('/start'))
+ assert page.status_code == 307
From 7af4147bcd5702cf4016291464f0e9ec82c79a4e Mon Sep 17 00:00:00 2001
From: leddest <46251307+leddest@users.noreply.github.com>
Date: Thu, 21 Jan 2021 18:16:21 +0200
Subject: [PATCH 03/16] Minor fixes
---
app/main.py | 2 --
app/routers/telegram.py | 4 ----
app/telegram/pylander.py | 2 --
dev.db-journal | Bin 0 -> 4612 bytes
tests/test_telegram.py | 2 --
5 files changed, 10 deletions(-)
create mode 100644 dev.db-journal
diff --git a/app/main.py b/app/main.py
index e2693e50..941702f7 100644
--- a/app/main.py
+++ b/app/main.py
@@ -1,6 +1,4 @@
from fastapi import FastAPI, Request
-from starlette.responses import RedirectResponse
-from starlette.status import HTTP_302_FOUND
from fastapi.staticfiles import StaticFiles
from app.database import models
diff --git a/app/routers/telegram.py b/app/routers/telegram.py
index b00e1c80..9c0b6187 100644
--- a/app/routers/telegram.py
+++ b/app/routers/telegram.py
@@ -1,8 +1,4 @@
from fastapi import APIRouter, Depends, Request
-import json
-import requests
-from starlette.responses import RedirectResponse
-from starlette.status import HTTP_302_FOUND
from app.database.database import get_db
from app.database.models import User
diff --git a/app/telegram/pylander.py b/app/telegram/pylander.py
index 89859c62..888af197 100644
--- a/app/telegram/pylander.py
+++ b/app/telegram/pylander.py
@@ -1,7 +1,5 @@
import os
-import os
-
from dotenv import load_dotenv
from app import config
diff --git a/dev.db-journal b/dev.db-journal
new file mode 100644
index 0000000000000000000000000000000000000000..bbe207ada4b40a7725b5b44fa0f51418fd508c59
GIT binary patch
literal 4612
zcmeH~!EVzq7{~3dOSh_BIT7`MWDOxT(u5d0BS<^9izb(X{Pk76aRap4HQ0I5
z_}%!`nAfjr*R`MPa+R+LC<2OrBA^H;0*Zhlpa>`e{|kY}4K$xM;YSt)^pairbVehd
z`Yc!swmL30J&Y{p5Pv{LAN3uyh(RqD7++>uMQ=9Yw;m^FAze6&d*!xbrBDR)Kn-zcSS|_GELdSR{_mOGczKgA%lWDCy$3QObVi!9de2S(iO;QY*RZ5kWvLto0!vQb(ex-UaF#CIh7w+DX!`ePG`
literal 0
HcmV?d00001
diff --git a/tests/test_telegram.py b/tests/test_telegram.py
index 5d5a47e3..31e4c83e 100644
--- a/tests/test_telegram.py
+++ b/tests/test_telegram.py
@@ -1,7 +1,5 @@
-import asyncio
import pytest
-from app import config
from app.telegram.handlers import MessageHandler, reply_unknown_user
from app.telegram.models import Bot, Chat
From 8433c4a6d1875d0fc35900231d946322cddf39eb Mon Sep 17 00:00:00 2001
From: leddest <46251307+leddest@users.noreply.github.com>
Date: Thu, 21 Jan 2021 18:20:19 +0200
Subject: [PATCH 04/16] Minor fixes
---
dev.db-journal | Bin 4612 -> 0 bytes
tests/test_telegram.py | 2 +-
2 files changed, 1 insertion(+), 1 deletion(-)
delete mode 100644 dev.db-journal
diff --git a/dev.db-journal b/dev.db-journal
deleted file mode 100644
index bbe207ada4b40a7725b5b44fa0f51418fd508c59..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 4612
zcmeH~!EVzq7{~3dOSh_BIT7`MWDOxT(u5d0BS<^9izb(X{Pk76aRap4HQ0I5
z_}%!`nAfjr*R`MPa+R+LC<2OrBA^H;0*Zhlpa>`e{|kY}4K$xM;YSt)^pairbVehd
z`Yc!swmL30J&Y{p5Pv{LAN3uyh(RqD7++>uMQ=9Yw;m^FAze6&d*!xbrBDR)Kn-zcSS|_GELdSR{_mOGczKgA%lWDCy$3QObVi!9de2S(iO;QY*RZ5kWvLto0!vQb(ex-UaF#CIh7w+DX!`ePG`
diff --git a/tests/test_telegram.py b/tests/test_telegram.py
index 31e4c83e..c9d3f614 100644
--- a/tests/test_telegram.py
+++ b/tests/test_telegram.py
@@ -39,7 +39,7 @@ def test_chat_model():
def test_bot_model():
bot = Bot("fake bot id", "https://google.com")
assert bot.base == 'https://api.telegram.org/botfake bot id/'
- assert bot.webhook_setter_url == f'https://api.telegram.org/botfake \
+ assert bot.webhook_setter_url == 'https://api.telegram.org/botfake \
bot id/setWebhook?url=https://google.com/telegram/'
assert bot.base == bot._set_base_url("fake bot id")
From 091db8ec8611feebe06ca189f6e49a6da9056f0b Mon Sep 17 00:00:00 2001
From: leddest <46251307+leddest@users.noreply.github.com>
Date: Fri, 22 Jan 2021 14:26:00 +0200
Subject: [PATCH 05/16] Create FastAPI Settings object
---
app/config.py.example | 15 ++++++++++-----
app/dependencies.py | 6 ++++++
app/main.py | 1 -
app/telegram/pylander.py | 11 ++++-------
4 files changed, 20 insertions(+), 13 deletions(-)
diff --git a/app/config.py.example b/app/config.py.example
index 098ef98f..993e2622 100644
--- a/app/config.py.example
+++ b/app/config.py.example
@@ -1,7 +1,16 @@
-# flake8: noqa
import os
from fastapi_mail import ConnectionConfig
+from pydantic import BaseSettings
+
+
+class Settings(BaseSettings):
+ app_name: str = "PyLander"
+ bot_api: str = "BOT_API"
+ webhook_url: str = "WEBHOOK_URL"
+
+ class Config:
+ env_file = ".env"
# DATABASE
@@ -12,10 +21,6 @@ MEDIA_DIRECTORY = 'media'
PICTURE_EXTENSION = '.png'
AVATAR_SIZE = (120, 120)
-# TELEGRAM
-BOT_API = "paste bot-api"
-WEBHOOK_URL = "use real webhook url or generate webhook for your local host with Ngrok"
-
# Email
email_conf = ConnectionConfig(
MAIL_USERNAME=os.getenv("MAIL_USERNAME") or "user",
diff --git a/app/dependencies.py b/app/dependencies.py
index 79ae18c5..bf3fe2ef 100644
--- a/app/dependencies.py
+++ b/app/dependencies.py
@@ -1,3 +1,4 @@
+from functools import lru_cache
import os
from fastapi.templating import Jinja2Templates
@@ -11,3 +12,8 @@
TEMPLATES_PATH = os.path.join(APP_PATH, "templates")
templates = Jinja2Templates(directory=TEMPLATES_PATH)
+
+
+@lru_cache()
+def get_settings():
+ return config.Settings()
diff --git a/app/main.py b/app/main.py
index 941702f7..36cd1585 100644
--- a/app/main.py
+++ b/app/main.py
@@ -8,7 +8,6 @@
from app.telegram.pylander import pylander
from app.routers import agenda, email, event, profile, telegram
-
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
diff --git a/app/telegram/pylander.py b/app/telegram/pylander.py
index 888af197..b19c31ab 100644
--- a/app/telegram/pylander.py
+++ b/app/telegram/pylander.py
@@ -1,14 +1,11 @@
-import os
-
-from dotenv import load_dotenv
-
from app import config
+from app.dependencies import get_settings
from .models import Bot
-load_dotenv()
+settings: config.Settings = get_settings()
-BOT_API = os.getenv("BOT_API", config.BOT_API)
-WEBHOOK_URL = os.getenv("WEBHOOK_URL", config.WEBHOOK_URL)
+BOT_API = settings.bot_api
+WEBHOOK_URL = settings.webhook_url
pylander = Bot(BOT_API, WEBHOOK_URL)
From 8e93a367ffb10491c887ce214cb0c6248ad99ff6 Mon Sep 17 00:00:00 2001
From: leddest <46251307+leddest@users.noreply.github.com>
Date: Fri, 22 Jan 2021 14:54:43 +0200
Subject: [PATCH 06/16] Use status code names in telegram tests
---
app/config.py.example | 2 +-
tests/test_telegram.py | 5 +++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/app/config.py.example b/app/config.py.example
index 993e2622..48d711cd 100644
--- a/app/config.py.example
+++ b/app/config.py.example
@@ -25,7 +25,7 @@ AVATAR_SIZE = (120, 120)
email_conf = ConnectionConfig(
MAIL_USERNAME=os.getenv("MAIL_USERNAME") or "user",
MAIL_PASSWORD=os.getenv("MAIL_PASSWORD") or "password",
- MAIL_FROM=os.getenv("MAIL_FROM") or "a@a.com",
+ MAIL_FROM=os.getenv("MAIL_FROM") or "a@a.com",
MAIL_PORT=587,
MAIL_SERVER="smtp.gmail.com",
MAIL_TLS=True,
diff --git a/tests/test_telegram.py b/tests/test_telegram.py
index c9d3f614..f4356db8 100644
--- a/tests/test_telegram.py
+++ b/tests/test_telegram.py
@@ -1,3 +1,4 @@
+from fastapi import status
import pytest
from app.telegram.handlers import MessageHandler, reply_unknown_user
@@ -118,7 +119,7 @@ async def test_reply_unknown_user():
def test_telegram_router(profile_test_client):
page = profile_test_client.get('/telegram')
data = page.content
- assert page.status_code == 200
+ assert page.ok is True
assert b"Start using PyLander telegram bot!" in data
@@ -126,4 +127,4 @@ def test_bot_client(profile_test_client):
page = profile_test_client.post(
'/telegram', data=gen_message('/start'))
- assert page.status_code == 307
+ assert page.status_code == status.HTTP_307_TEMPORARY_REDIRECT
From b8d0cbaf1cb0d6d9c1aff48d9c706bc5261214b5 Mon Sep 17 00:00:00 2001
From: leddest <46251307+leddest@users.noreply.github.com>
Date: Fri, 22 Jan 2021 15:30:41 +0200
Subject: [PATCH 07/16] Use status code names in telegram tests
---
tests/test_telegram.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/test_telegram.py b/tests/test_telegram.py
index f4356db8..a81403bd 100644
--- a/tests/test_telegram.py
+++ b/tests/test_telegram.py
@@ -48,7 +48,7 @@ def test_bot_model():
"https://google.com")
set_request = bot.set_webhook()
- assert set_request.status_code == 404
+ assert set_request.status_code == status.HTTP_404_NOT_FOUND
assert set_request.json() == {
'ok': False,
'error_code': 404,
From 198e4d69f19d301ad4a2f2cb13ee22eb2156c126 Mon Sep 17 00:00:00 2001
From: leddest <46251307+leddest@users.noreply.github.com>
Date: Sat, 23 Jan 2021 18:59:28 +0200
Subject: [PATCH 08/16] Add Asynchronous Testing and Increase Code Coverage.
---
tests/test_telegram.py | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/tests/test_telegram.py b/tests/test_telegram.py
index a81403bd..efb84b3f 100644
--- a/tests/test_telegram.py
+++ b/tests/test_telegram.py
@@ -1,6 +1,8 @@
from fastapi import status
+from httpx import AsyncClient
import pytest
+from app.main import app
from app.telegram.handlers import MessageHandler, reply_unknown_user
from app.telegram.models import Bot, Chat
@@ -118,13 +120,14 @@ async def test_reply_unknown_user():
def test_telegram_router(profile_test_client):
page = profile_test_client.get('/telegram')
- data = page.content
- assert page.ok is True
- assert b"Start using PyLander telegram bot!" in data
+ assert page.ok
+ assert b"Start using PyLander telegram bot!" in page.content
-def test_bot_client(profile_test_client):
-
- page = profile_test_client.post(
- '/telegram', data=gen_message('/start'))
- assert page.status_code == status.HTTP_307_TEMPORARY_REDIRECT
+@pytest.mark.asyncio
+async def test_bot_client(profile_test_client):
+ async with AsyncClient(app=app, base_url="http://test") as ac:
+ req = await ac.post('/telegram/', json=gen_message('/start'))
+ assert req.status_code == status.HTTP_200_OK
+ assert b'Hello, Moshe!' in req.content
+ assert b'To use PyLander Bot you have to register' in req.content
From 3fc9d4984891f81741c8acebc9dd63230db38efd Mon Sep 17 00:00:00 2001
From: leddest <46251307+leddest@users.noreply.github.com>
Date: Sun, 24 Jan 2021 00:36:47 +0200
Subject: [PATCH 09/16] Added keyboards, handlers and tests. Updated telegram
models.
---
app/routers/telegram.py | 12 ++--
app/telegram/handlers.py | 88 +++++++++++++++++++++-----
app/telegram/keyboards.py | 49 ++++++++++++++
app/telegram/models.py | 47 ++++++++++----
tests/test_telegram.py | 130 ++++++++++++++++++++++++++++++--------
5 files changed, 261 insertions(+), 65 deletions(-)
diff --git a/app/routers/telegram.py b/app/routers/telegram.py
index 9c0b6187..cda82111 100644
--- a/app/routers/telegram.py
+++ b/app/routers/telegram.py
@@ -24,14 +24,12 @@ async def telegram(request: Request, session=Depends(get_db)):
async def bot_client(request: Request, session=Depends(get_db)):
req = await request.json()
chat = Chat(req)
- message = MessageHandler(chat)
- # Check if current chatter in DB
+ # Check if current chatter is registered to use the bot
user = session.query(User).filter_by(telegram_id=chat.user_id).first()
-
if user is None:
return await reply_unknown_user(chat)
- else:
- # Process reply
- message.process_callback()
- return user
+
+ message = MessageHandler(chat, user)
+ # Process reply
+ return message.process_callback()
diff --git a/app/telegram/handlers.py b/app/telegram/handlers.py
index 042066c7..0d8637b9 100644
--- a/app/telegram/handlers.py
+++ b/app/telegram/handlers.py
@@ -1,29 +1,34 @@
+import datetime
+
+from .keyboards import (
+ DATE_FORMAT, gen_inline_keyboard, get_this_week_buttons, show_events_kb)
from .models import Chat
from .pylander import pylander
+from app.database.models import User
class MessageHandler:
- COMMANDS = ['/start', '/logout', '/show_events', '/new_event']
-
- def __init__(self, chat: Chat):
+ def __init__(self, chat: Chat, user: User):
self.chat = chat
+ self.user = user
self.handlers = {}
- for method in dir(self):
- if method.endswith('_handler'):
- method_name = method[:-len('_handler')]
- self.handlers[method_name] = getattr(MessageHandler, method)
+ self.handlers['/start'] = self.start_handler
+ self.handlers['/show_events'] = self.show_events_handler
+ self.handlers['Today'] = self.today_handler
+ self.handlers['This week'] = self.this_week_handler
+
+ # Add next 6 days to handlers dict
+ for row in get_this_week_buttons():
+ for button in row:
+ self.handlers[button['text']] = self.chosen_day_handler
def process_callback(self):
- if self.chat.message in self.COMMANDS:
- return self.handlers[self.chat.message[1:]](self)
- elif (self.chat.message in self.handlers
- and f'/{self.chat.message}' not in self.COMMANDS):
- return self.handlers[self.chat.message](self)
- else:
- return self.default_handler()
+ if self.chat.message in self.handlers:
+ return self.handlers[self.chat.message]()
+ return self.default_handler()
def default_handler(self):
- answer = "Unknown command"
+ answer = "Unknown command."
pylander.send_message(chat_id=self.chat.user_id, text=answer)
return answer
@@ -34,7 +39,58 @@ def start_handler(self):
return answer
def show_events_handler(self):
- answer = 'Choose events day'
+ answer = 'Choose events day.'
+ pylander.send_message(
+ chat_id=self.chat.user_id,
+ text=answer,
+ reply_markup=show_events_kb)
+ return answer
+
+ def today_handler(self):
+ today = datetime.date.today()
+ events = [
+ event for event in self.user.events
+ if event.start <= today <= event.end]
+
+ answer = f"{today.strftime('%B %d')}, {today.strftime('%A')} Events:\n"
+
+ if not len(events):
+ answer = "There're no events today."
+
+ for event in events:
+ answer += f'\n\n{event.title}: from {event.start} to {event.ends}.'
+
+ pylander.send_message(chat_id=self.chat.user_id, text=answer)
+ return answer
+
+ def this_week_handler(self):
+ answer = 'Choose a day.'
+ this_week_kb = gen_inline_keyboard(get_this_week_buttons())
+
+ pylander.send_message(
+ chat_id=self.chat.user_id,
+ text=answer,
+ reply_markup=this_week_kb)
+ return answer
+
+ def chosen_day_handler(self):
+ # Convert chosen day (string) to datetime format
+ chosen_date = datetime.datetime.strptime(
+ self.chat.message, DATE_FORMAT)
+
+ events = [
+ event for event in self.user.events
+ if event.start <= chosen_date <= event.end]
+
+ answer = f"{chosen_date.strftime('%B %d')}, \
+{chosen_date.strftime('%A')} Events:\n"
+
+ if not len(events):
+ answer = f"There're no events on {chosen_date.strftime('%B %d')}."
+
+ for event in events:
+ answer += f'\n\n{event.title}: from {event.start} to {event.ends}.'
+
pylander.send_message(chat_id=self.chat.user_id, text=answer)
return answer
diff --git a/app/telegram/keyboards.py b/app/telegram/keyboards.py
index e69de29b..e4f65b8c 100644
--- a/app/telegram/keyboards.py
+++ b/app/telegram/keyboards.py
@@ -0,0 +1,49 @@
+import datetime
+import json
+from typing import Any, Dict, List
+
+
+show_events_buttons = [
+ [
+ {'text': 'Today', 'callback_data': 'Today'},
+ {'text': 'This week', 'callback_data': 'This week'}
+ ]
+]
+
+DATE_FORMAT = '%d %b %Y'
+
+
+def get_this_week_buttons() -> List[List[Any]]:
+ today = datetime.date.today()
+ day1 = today + datetime.timedelta(days=1)
+ day2 = today + datetime.timedelta(days=2)
+ day3 = today + datetime.timedelta(days=3)
+ day4 = today + datetime.timedelta(days=4)
+ day5 = today + datetime.timedelta(days=5)
+ day6 = today + datetime.timedelta(days=6)
+
+ return [
+ [
+ {'text': day1.strftime(DATE_FORMAT),
+ 'callback_data': day1.strftime(DATE_FORMAT)},
+ {'text': day2.strftime(DATE_FORMAT),
+ 'callback_data': day2.strftime(DATE_FORMAT)},
+ {'text': day3.strftime(DATE_FORMAT),
+ 'callback_data': day3.strftime(DATE_FORMAT)}
+ ],
+ [
+ {'text': day4.strftime(DATE_FORMAT),
+ 'callback_data': day4.strftime(DATE_FORMAT)},
+ {'text': day5.strftime(DATE_FORMAT),
+ 'callback_data': day5.strftime(DATE_FORMAT)},
+ {'text': day6.strftime(DATE_FORMAT),
+ 'callback_data': day6.strftime(DATE_FORMAT)}
+ ]
+ ]
+
+
+def gen_inline_keyboard(buttons: List[List[Any]]) -> Dict[str, Any]:
+ return {'reply_markup': json.dumps({'inline_keyboard': buttons})}
+
+
+show_events_kb = gen_inline_keyboard(show_events_buttons)
diff --git a/app/telegram/models.py b/app/telegram/models.py
index 83d65412..3440c574 100644
--- a/app/telegram/models.py
+++ b/app/telegram/models.py
@@ -1,40 +1,59 @@
+from typing import Any, Dict, Optional
+
import requests
class Chat:
- def __init__(self, message):
- self.message = message['message']['text']
- self.user_id = message['message']['from']['id']
- self.username = message['message']['from']['username']
- self.first_name = message['message']['from']['first_name']
+ def __init__(self, data):
+ self.message = self._get_message_content(data)
+ self.user_id = self._get_user_id(data)
+ self.first_name = self._get_first_name(data)
+
+ def _get_message_content(self, data):
+ if 'callback_query' in data:
+ return data['callback_query']['data']
+ return data['message']['text']
+
+ def _get_user_id(self, data):
+ if 'callback_query' in data:
+ return data['callback_query']['from']['id']
+ return data['message']['from']['id']
+
+ def _get_first_name(self, data):
+ if 'callback_query' in data:
+ return data['callback_query']['from']['first_name']
+ return data['message']['from']['first_name']
class Bot:
- def __init__(self, bot_api, webhook_url):
+ def __init__(self, bot_api: str, webhook_url: str):
self.base = self._set_base_url(bot_api)
self.webhook_setter_url = self._set_webhook_setter_url(webhook_url)
def _set_base_url(self, bot_api):
return f'https://api.telegram.org/bot{bot_api}/'
- def _set_webhook_setter_url(self, webhook_url):
+ def _set_webhook_setter_url(self, webhook_url: str):
return f'{self.base}setWebhook?url={webhook_url}/telegram/'
def set_webhook(self):
return requests.get(self.webhook_setter_url)
def drop_webhook(self):
- url = f'{self.base}deleteWebhook'
data = {
'drop_pending_updates': True
}
- return requests.get(url, data=data)
+ return requests.get(
+ url=f'{self.base}deleteWebhook', data=data)
- def send_message(self, chat_id, text, reply_markup=[]):
+ def send_message(
+ self, chat_id: str,
+ text: str,
+ reply_markup: Optional[Dict[str, Any]] = None):
message = {
'chat_id': chat_id,
'text': text,
- 'reply_markup': reply_markup
- }
- url = f'{self.base}sendMessage'
- return requests.post(url, data=message)
+ 'reply_markup': None}
+ if reply_markup:
+ message.update(reply_markup)
+ return requests.post(f'{self.base}sendMessage', data=message)
diff --git a/tests/test_telegram.py b/tests/test_telegram.py
index efb84b3f..3e56c0d7 100644
--- a/tests/test_telegram.py
+++ b/tests/test_telegram.py
@@ -2,6 +2,7 @@
from httpx import AsyncClient
import pytest
+from .conftest import get_test_placeholder_user
from app.main import app
from app.telegram.handlers import MessageHandler, reply_unknown_user
from app.telegram.models import Bot, Chat
@@ -31,12 +32,66 @@ def gen_message(text):
}
-def test_chat_model():
- chat = Chat(gen_message('Cool message'))
- assert chat.message == 'Cool message'
- assert chat.user_id == 666666
- assert chat.username == 'banana'
- assert chat.first_name == 'Moshe'
+def gen_callback(text):
+ return {
+ 'update_id': 568265,
+ 'callback_query': {
+ 'id': '546565356486',
+ 'from': {
+ 'id': 666666,
+ 'is_bot': False,
+ 'first_name': 'Moshe',
+ 'username': 'banana',
+ 'language_code': 'en'
+ }, 'message': {
+ 'message_id': 838,
+ 'from': {
+ 'id': 2566252,
+ 'is_bot': True,
+ 'first_name': 'PyLandar',
+ 'username': 'pylander_bot'
+ }, 'chat': {
+ 'id': 666666,
+ 'first_name': 'Moshe',
+ 'username': 'banana',
+ 'type': 'private'
+ },
+ 'date': 161156,
+ 'text': 'Choose events day.',
+ 'reply_markup': {
+ 'inline_keyboard': [
+ [
+ {
+ 'text': 'Today',
+ 'callback_data': 'Today'
+ },
+ {
+ 'text': 'This week',
+ 'callback_data': 'This week'
+ }
+ ]
+ ]
+ }
+ },
+ 'chat_instance': '-154494',
+ 'data': f'{text}'}}
+
+
+class TestChatModel:
+
+ @staticmethod
+ def test_private_message():
+ chat = Chat(gen_message('Cool message'))
+ assert chat.message == 'Cool message'
+ assert chat.user_id == 666666
+ assert chat.first_name == 'Moshe'
+
+ @staticmethod
+ def test_callback_message():
+ chat = Chat(gen_callback('Callback Message'))
+ assert chat.message == 'Callback Message'
+ assert chat.user_id == 666666
+ assert chat.first_name == 'Moshe'
def test_bot_model():
@@ -74,31 +129,50 @@ def test_bot_model():
}
-def test_start_handlers():
- chat = Chat(gen_message('/start'))
- message = MessageHandler(chat)
-
- assert 'start' in message.handlers
- assert 'default' in message.handlers
- assert 'show_events' in message.handlers
- assert message.process_callback() == f'''Hello, {message.chat.first_name}!
-Welcome to Pylander telegram client!'''
+class TestHandlers:
+ TEST_USER = get_test_placeholder_user()
+ def test_start_handlers(self):
+ chat = Chat(gen_message('/start'))
+ message = MessageHandler(chat, self.TEST_USER)
-def test_default_handlers():
- wrong_start = MessageHandler(Chat(gen_message('start')))
- wrong_show_events = MessageHandler(Chat(gen_message('start')))
- message = MessageHandler(Chat(gen_message('hello')))
-
- assert wrong_start.process_callback() == "Unknown command"
- assert wrong_show_events.process_callback() == "Unknown command"
- assert message.process_callback() == "Unknown command"
-
+ assert '/start' in message.handlers
+ assert message.process_callback() == f'''Hello, Moshe!
+Welcome to Pylander telegram client!'''
-def test_show_events_handler():
- chat = Chat(gen_message('/show_events'))
- message = MessageHandler(chat)
- assert message.process_callback() == 'Choose events day'
+ def test_default_handlers(self):
+ wrong_start = MessageHandler(
+ Chat(gen_message('start')), self.TEST_USER)
+ wrong_show_events = MessageHandler(
+ Chat(gen_message('show_events')), self.TEST_USER)
+ message = MessageHandler(
+ Chat(gen_message('hello')), self.TEST_USER)
+
+ assert wrong_start.process_callback() == "Unknown command."
+ assert wrong_show_events.process_callback() == "Unknown command."
+ assert message.process_callback() == "Unknown command."
+
+ def test_show_events_handler(self):
+ chat = Chat(gen_message('/show_events'))
+ message = MessageHandler(chat, self.TEST_USER)
+ assert message.process_callback() == 'Choose events day.'
+
+ def test_today_handler(self):
+ chat = Chat(gen_callback('Today'))
+ message = MessageHandler(chat, self.TEST_USER)
+ assert message.process_callback() == "There're no events today."
+
+ def test_this_week_handler(self):
+ chat = Chat(gen_callback('This week'))
+ message = MessageHandler(chat, self.TEST_USER)
+ assert message.process_callback() == 'Choose a day.'
+
+ def test_chosen_day_handler(self):
+ chat = Chat(gen_callback('10 Feb 2021'))
+ message = MessageHandler(chat, self.TEST_USER)
+ message.handlers['10 Feb 2021'] = message.chosen_day_handler
+ assert (
+ message.process_callback() == "There're no events on February 10.")
@pytest.mark.asyncio
From 30786825ee1475c554664f492fa45b103b211ca7 Mon Sep 17 00:00:00 2001
From: leddest <46251307+leddest@users.noreply.github.com>
Date: Sun, 24 Jan 2021 04:34:10 +0200
Subject: [PATCH 10/16] Minor fixes.
---
app/config.py.example | 1 -
tests/test_telegram.py | 2 +-
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/app/config.py.example b/app/config.py.example
index b9e9fc7b..71177a80 100644
--- a/app/config.py.example
+++ b/app/config.py.example
@@ -12,7 +12,6 @@ class Settings(BaseSettings):
class Config:
env_file = ".env"
-# flake8: noqa
# general
DOMAIN = 'Our-Domain'
diff --git a/tests/test_telegram.py b/tests/test_telegram.py
index ac1aca26..91e25dcc 100644
--- a/tests/test_telegram.py
+++ b/tests/test_telegram.py
@@ -134,7 +134,7 @@ def test_start_handlers(self):
message = MessageHandler(chat, self.TEST_USER)
assert '/start' in message.handlers
- assert message.process_callback() == f'''Hello, Moshe!
+ assert message.process_callback() == '''Hello, Moshe!
Welcome to Pylander telegram client!'''
def test_default_handlers(self):
From 94a8c012a733c42735af021f4dee617a8c22d5f2 Mon Sep 17 00:00:00 2001
From: leddest <46251307+leddest@users.noreply.github.com>
Date: Sun, 24 Jan 2021 15:01:07 +0200
Subject: [PATCH 11/16] Add test bot client with registered user
---
app/routers/telegram.py | 1 +
tests/client_fixture.py | 2 +-
tests/test_telegram.py | 29 ++++++++++++++++++++---------
3 files changed, 22 insertions(+), 10 deletions(-)
diff --git a/app/routers/telegram.py b/app/routers/telegram.py
index b8154549..eca21231 100644
--- a/app/routers/telegram.py
+++ b/app/routers/telegram.py
@@ -26,6 +26,7 @@ def bot_client(req: dict = Body(...), session=Depends(get_db)):
# Check if current chatter is registered to use the bot
user = session.query(User).filter_by(telegram_id=chat.user_id).first()
+ print(user)
if user is None:
return reply_unknown_user(chat)
diff --git a/tests/client_fixture.py b/tests/client_fixture.py
index 53b718df..b72a3faa 100644
--- a/tests/client_fixture.py
+++ b/tests/client_fixture.py
@@ -57,7 +57,7 @@ def get_test_placeholder_user():
email='fake@mail.fake',
password='123456fake',
full_name='FakeName',
- telegram_id=''
+ telegram_id='666666'
)
diff --git a/tests/test_telegram.py b/tests/test_telegram.py
index 91e25dcc..a1cf94af 100644
--- a/tests/test_telegram.py
+++ b/tests/test_telegram.py
@@ -188,14 +188,25 @@ def test_reply_unknown_user():
'''
-def test_telegram_router(profile_test_client):
- req = profile_test_client.get('/telegram')
- assert req.ok
- assert b"Start using PyLander telegram bot!" in req.content
+class TestBotClient:
+ @staticmethod
+ def test_telegram_router(profile_test_client):
+ req = profile_test_client.get('/telegram')
+ assert req.ok
+ assert b"Start using PyLander telegram bot!" in req.content
+
+ @staticmethod
+ def test_user_not_registered(telegram_client):
+ req = telegram_client.post('/telegram/', json=gen_message('/start'))
+ assert req.ok
+ assert b'Hello, Moshe!' in req.content
+ assert b'To use PyLander Bot you have to register' in req.content
-def test_bot_client(telegram_client):
- req = telegram_client.post('/telegram/', json=gen_message('/start'))
- assert req.ok
- assert b'Hello, Moshe!' in req.content
- assert b'To use PyLander Bot you have to register' in req.content
+ @staticmethod
+ def test_user_registered(telegram_client, session):
+ session.add(get_test_placeholder_user())
+ session.commit()
+ req = telegram_client.post('/telegram/', json=gen_message('/start'))
+ assert req.ok
+ assert b'Welcome to Pylander telegram client!' in req.content
From b0bc592ca0511c100c46a6ce7d376b327e54b215 Mon Sep 17 00:00:00 2001
From: leddest <46251307+leddest@users.noreply.github.com>
Date: Sun, 24 Jan 2021 15:04:19 +0200
Subject: [PATCH 12/16] Minor fix
---
app/routers/telegram.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/app/routers/telegram.py b/app/routers/telegram.py
index eca21231..b8154549 100644
--- a/app/routers/telegram.py
+++ b/app/routers/telegram.py
@@ -26,7 +26,6 @@ def bot_client(req: dict = Body(...), session=Depends(get_db)):
# Check if current chatter is registered to use the bot
user = session.query(User).filter_by(telegram_id=chat.user_id).first()
- print(user)
if user is None:
return reply_unknown_user(chat)
From de2995ca22d1084c7e12b2856c608cf451c3c7f8 Mon Sep 17 00:00:00 2001
From: leddest <46251307+leddest@users.noreply.github.com>
Date: Sun, 24 Jan 2021 18:52:24 +0200
Subject: [PATCH 13/16] Make telegram bot working async. Add async fixture and
tests.
---
app/main.py | 4 +-
app/routers/telegram.py | 6 +-
app/telegram/{pylander.py => bot.py} | 2 +-
app/telegram/handlers.py | 38 ++++-----
app/telegram/keyboards.py | 34 ++++----
app/telegram/models.py | 21 ++---
tests/asyncio_fixture.py | 17 ++++
tests/client_fixture.py | 14 +---
tests/conftest.py | 1 +
...telegram.py => test_a_telegram_asyncio.py} | 83 +++++++++++--------
10 files changed, 118 insertions(+), 102 deletions(-)
rename app/telegram/{pylander.py => bot.py} (82%)
create mode 100644 tests/asyncio_fixture.py
rename tests/{test_telegram.py => test_a_telegram_asyncio.py} (70%)
diff --git a/app/main.py b/app/main.py
index 8e248296..7498af76 100644
--- a/app/main.py
+++ b/app/main.py
@@ -5,7 +5,7 @@
from app.database.database import engine
from app.dependencies import (
MEDIA_PATH, STATIC_PATH, templates)
-from app.telegram.pylander import pylander
+from app.telegram.bot import telegram_bot
from app.routers import agenda, email, event, invitation, profile, telegram
models.Base.metadata.create_all(bind=engine)
@@ -21,7 +21,7 @@
app.include_router(email.router)
app.include_router(invitation.router)
-pylander.set_webhook()
+telegram_bot.set_webhook()
@app.get("/")
diff --git a/app/routers/telegram.py b/app/routers/telegram.py
index b8154549..5c5e9d7b 100644
--- a/app/routers/telegram.py
+++ b/app/routers/telegram.py
@@ -21,13 +21,13 @@ async def telegram(request: Request, session=Depends(get_db)):
@router.post("/")
-def bot_client(req: dict = Body(...), session=Depends(get_db)):
+async def bot_client(req: dict = Body(...), session=Depends(get_db)):
chat = Chat(req)
# Check if current chatter is registered to use the bot
user = session.query(User).filter_by(telegram_id=chat.user_id).first()
if user is None:
- return reply_unknown_user(chat)
+ return await reply_unknown_user(chat)
message = MessageHandler(chat, user)
- return message.process_callback()
+ return await message.process_callback()
diff --git a/app/telegram/pylander.py b/app/telegram/bot.py
similarity index 82%
rename from app/telegram/pylander.py
rename to app/telegram/bot.py
index b19c31ab..30aeaf6c 100644
--- a/app/telegram/pylander.py
+++ b/app/telegram/bot.py
@@ -8,4 +8,4 @@
BOT_API = settings.bot_api
WEBHOOK_URL = settings.webhook_url
-pylander = Bot(BOT_API, WEBHOOK_URL)
+telegram_bot = Bot(BOT_API, WEBHOOK_URL)
diff --git a/app/telegram/handlers.py b/app/telegram/handlers.py
index 80608529..5a30c191 100644
--- a/app/telegram/handlers.py
+++ b/app/telegram/handlers.py
@@ -3,7 +3,7 @@
from .keyboards import (
DATE_FORMAT, gen_inline_keyboard, get_this_week_buttons, show_events_kb)
from .models import Chat
-from .pylander import pylander
+from .bot import telegram_bot
from app.database.models import User
@@ -22,31 +22,31 @@ def __init__(self, chat: Chat, user: User):
for button in row:
self.handlers[button['text']] = self.chosen_day_handler
- def process_callback(self):
+ async def process_callback(self):
if self.chat.message in self.handlers:
- return self.handlers[self.chat.message]()
- return self.default_handler()
+ return await self.handlers[self.chat.message]()
+ return await self.default_handler()
- def default_handler(self):
+ async def default_handler(self):
answer = "Unknown command."
- pylander.send_message(chat_id=self.chat.user_id, text=answer)
+ await telegram_bot.send_message(chat_id=self.chat.user_id, text=answer)
return answer
- def start_handler(self):
+ async def start_handler(self):
answer = f'''Hello, {self.chat.first_name}!
Welcome to Pylander telegram client!'''
- pylander.send_message(chat_id=self.chat.user_id, text=answer)
+ await telegram_bot.send_message(chat_id=self.chat.user_id, text=answer)
return answer
- def show_events_handler(self):
+ async def show_events_handler(self):
answer = 'Choose events day.'
- pylander.send_message(
+ await telegram_bot.send_message(
chat_id=self.chat.user_id,
text=answer,
reply_markup=show_events_kb)
return answer
- def today_handler(self):
+ async def today_handler(self):
today = datetime.date.today()
events = [
event for event in self.user.events
@@ -60,20 +60,20 @@ def today_handler(self):
for event in events:
answer += f'\n\n{event.title}: from {event.start} to {event.ends}.'
- pylander.send_message(chat_id=self.chat.user_id, text=answer)
+ await telegram_bot.send_message(chat_id=self.chat.user_id, text=answer)
return answer
- def this_week_handler(self):
+ async def this_week_handler(self):
answer = 'Choose a day.'
this_week_kb = gen_inline_keyboard(get_this_week_buttons())
- pylander.send_message(
+ await telegram_bot.send_message(
chat_id=self.chat.user_id,
text=answer,
reply_markup=this_week_kb)
return answer
- def chosen_day_handler(self):
+ async def chosen_day_handler(self):
# Convert chosen day (string) to datetime format
chosen_date = datetime.datetime.strptime(
self.chat.message, DATE_FORMAT)
@@ -85,17 +85,17 @@ def chosen_day_handler(self):
answer = f"{chosen_date.strftime('%B %d')}, \
{chosen_date.strftime('%A')} Events:\n"
- if not len(events):
+ if not events:
answer = f"There're no events on {chosen_date.strftime('%B %d')}."
for event in events:
answer += f'\n\n{event.title}: from {event.start} to {event.ends}.'
- pylander.send_message(chat_id=self.chat.user_id, text=answer)
+ await telegram_bot.send_message(chat_id=self.chat.user_id, text=answer)
return answer
-def reply_unknown_user(chat):
+async def reply_unknown_user(chat):
answer = f'''
Hello, {chat.first_name}!
@@ -107,5 +107,5 @@ def reply_unknown_user(chat):
https://calendar.pythonic.guru/profile/
'''
- pylander.send_message(chat_id=chat.user_id, text=answer)
+ await telegram_bot.send_message(chat_id=chat.user_id, text=answer)
return answer
diff --git a/app/telegram/keyboards.py b/app/telegram/keyboards.py
index e4f65b8c..6a0251e5 100644
--- a/app/telegram/keyboards.py
+++ b/app/telegram/keyboards.py
@@ -15,29 +15,27 @@
def get_this_week_buttons() -> List[List[Any]]:
today = datetime.date.today()
- day1 = today + datetime.timedelta(days=1)
- day2 = today + datetime.timedelta(days=2)
- day3 = today + datetime.timedelta(days=3)
- day4 = today + datetime.timedelta(days=4)
- day5 = today + datetime.timedelta(days=5)
- day6 = today + datetime.timedelta(days=6)
+ buttons = []
+ for day in range(1, 7):
+ day = today + datetime.timedelta(days=day)
+ buttons.append(day.strftime(DATE_FORMAT))
return [
[
- {'text': day1.strftime(DATE_FORMAT),
- 'callback_data': day1.strftime(DATE_FORMAT)},
- {'text': day2.strftime(DATE_FORMAT),
- 'callback_data': day2.strftime(DATE_FORMAT)},
- {'text': day3.strftime(DATE_FORMAT),
- 'callback_data': day3.strftime(DATE_FORMAT)}
+ {'text': buttons[0],
+ 'callback_data': buttons[0]},
+ {'text': buttons[1],
+ 'callback_data': buttons[1]},
+ {'text': buttons[2],
+ 'callback_data': buttons[2]}
],
[
- {'text': day4.strftime(DATE_FORMAT),
- 'callback_data': day4.strftime(DATE_FORMAT)},
- {'text': day5.strftime(DATE_FORMAT),
- 'callback_data': day5.strftime(DATE_FORMAT)},
- {'text': day6.strftime(DATE_FORMAT),
- 'callback_data': day6.strftime(DATE_FORMAT)}
+ {'text': buttons[3],
+ 'callback_data': buttons[3]},
+ {'text': buttons[4],
+ 'callback_data': buttons[4]},
+ {'text': buttons[5],
+ 'callback_data': buttons[5]}
]
]
diff --git a/app/telegram/models.py b/app/telegram/models.py
index 3440c574..be6492da 100644
--- a/app/telegram/models.py
+++ b/app/telegram/models.py
@@ -4,22 +4,22 @@
class Chat:
- def __init__(self, data):
+ def __init__(self, data: Dict):
self.message = self._get_message_content(data)
self.user_id = self._get_user_id(data)
self.first_name = self._get_first_name(data)
- def _get_message_content(self, data):
+ def _get_message_content(self, data: Dict) -> str:
if 'callback_query' in data:
return data['callback_query']['data']
return data['message']['text']
- def _get_user_id(self, data):
+ def _get_user_id(self, data: Dict) -> str:
if 'callback_query' in data:
return data['callback_query']['from']['id']
return data['message']['from']['id']
- def _get_first_name(self, data):
+ def _get_first_name(self, data: Dict) -> str:
if 'callback_query' in data:
return data['callback_query']['from']['first_name']
return data['message']['from']['first_name']
@@ -30,23 +30,20 @@ def __init__(self, bot_api: str, webhook_url: str):
self.base = self._set_base_url(bot_api)
self.webhook_setter_url = self._set_webhook_setter_url(webhook_url)
- def _set_base_url(self, bot_api):
+ def _set_base_url(self, bot_api: str) -> str:
return f'https://api.telegram.org/bot{bot_api}/'
- def _set_webhook_setter_url(self, webhook_url: str):
+ def _set_webhook_setter_url(self, webhook_url: str) -> str:
return f'{self.base}setWebhook?url={webhook_url}/telegram/'
def set_webhook(self):
return requests.get(self.webhook_setter_url)
def drop_webhook(self):
- data = {
- 'drop_pending_updates': True
- }
- return requests.get(
- url=f'{self.base}deleteWebhook', data=data)
+ data = {'drop_pending_updates': True}
+ return requests.get(url=f'{self.base}deleteWebhook', data=data)
- def send_message(
+ async def send_message(
self, chat_id: str,
text: str,
reply_markup: Optional[Dict[str, Any]] = None):
diff --git a/tests/asyncio_fixture.py b/tests/asyncio_fixture.py
new file mode 100644
index 00000000..a0b5b6d2
--- /dev/null
+++ b/tests/asyncio_fixture.py
@@ -0,0 +1,17 @@
+from httpx import AsyncClient
+import pytest
+
+from app.database.database import Base
+from app.main import app
+from app.routers import telegram
+from tests.conftest import test_engine, get_test_db
+
+
+@pytest.fixture
+async def telegram_client():
+ Base.metadata.create_all(bind=test_engine)
+ app.dependency_overrides[telegram.get_db] = get_test_db
+ async with AsyncClient(app=app, base_url="http://test") as ac:
+ yield ac
+ app.dependency_overrides = {}
+ Base.metadata.drop_all(bind=test_engine)
diff --git a/tests/client_fixture.py b/tests/client_fixture.py
index b72a3faa..edc24889 100644
--- a/tests/client_fixture.py
+++ b/tests/client_fixture.py
@@ -4,7 +4,7 @@
from app.database.models import User
from app.main import app
from app.database.database import Base
-from app.routers import agenda, invitation, profile, telegram
+from app.routers import agenda, invitation, profile
from tests.conftest import test_engine, get_test_db
@@ -59,15 +59,3 @@ def get_test_placeholder_user():
full_name='FakeName',
telegram_id='666666'
)
-
-
-@pytest.fixture(scope="module")
-def telegram_client():
- Base.metadata.create_all(bind=test_engine)
- app.dependency_overrides[telegram.get_db] = get_test_db
-
- with TestClient(app) as client:
- yield client
-
- app.dependency_overrides = {}
- Base.metadata.drop_all(bind=test_engine)
diff --git a/tests/conftest.py b/tests/conftest.py
index a838375d..f0d83167 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -10,6 +10,7 @@
'tests.invitation_fixture',
'tests.association_fixture',
'tests.client_fixture',
+ 'tests.asyncio_fixture',
'smtpdfix',
]
diff --git a/tests/test_telegram.py b/tests/test_a_telegram_asyncio.py
similarity index 70%
rename from tests/test_telegram.py
rename to tests/test_a_telegram_asyncio.py
index a1cf94af..26b0c0bd 100644
--- a/tests/test_telegram.py
+++ b/tests/test_a_telegram_asyncio.py
@@ -1,4 +1,5 @@
from fastapi import status
+import pytest
from .client_fixture import get_test_placeholder_user
from app.telegram.handlers import MessageHandler, reply_unknown_user
@@ -91,7 +92,8 @@ def test_callback_message():
assert chat.first_name == 'Moshe'
-def test_bot_model():
+@pytest.mark.asyncio
+async def test_bot_model():
bot = Bot("fake bot id", "https://google.com")
assert bot.base == 'https://api.telegram.org/botfake bot id/'
assert bot.webhook_setter_url == 'https://api.telegram.org/botfake \
@@ -117,7 +119,7 @@ def test_bot_model():
'description': 'Not Found'
}
- send_request = bot.send_message("654654645", "hello")
+ send_request = await bot.send_message("654654645", "hello")
assert send_request.status_code == status.HTTP_404_NOT_FOUND
assert send_request.json() == {
'ok': False,
@@ -129,15 +131,17 @@ def test_bot_model():
class TestHandlers:
TEST_USER = get_test_placeholder_user()
- def test_start_handlers(self):
+ @pytest.mark.asyncio
+ async def test_start_handlers(self):
chat = Chat(gen_message('/start'))
message = MessageHandler(chat, self.TEST_USER)
assert '/start' in message.handlers
- assert message.process_callback() == '''Hello, Moshe!
+ assert await message.process_callback() == '''Hello, Moshe!
Welcome to Pylander telegram client!'''
- def test_default_handlers(self):
+ @pytest.mark.asyncio
+ async def test_default_handlers(self):
wrong_start = MessageHandler(
Chat(gen_message('start')), self.TEST_USER)
wrong_show_events = MessageHandler(
@@ -145,36 +149,41 @@ def test_default_handlers(self):
message = MessageHandler(
Chat(gen_message('hello')), self.TEST_USER)
- assert wrong_start.process_callback() == "Unknown command."
- assert wrong_show_events.process_callback() == "Unknown command."
- assert message.process_callback() == "Unknown command."
+ assert await wrong_start.process_callback() == "Unknown command."
+ assert await wrong_show_events.process_callback() == "Unknown command."
+ assert await message.process_callback() == "Unknown command."
- def test_show_events_handler(self):
+ @pytest.mark.asyncio
+ async def test_show_events_handler(self):
chat = Chat(gen_message('/show_events'))
message = MessageHandler(chat, self.TEST_USER)
- assert message.process_callback() == 'Choose events day.'
+ assert await message.process_callback() == 'Choose events day.'
- def test_today_handler(self):
+ @pytest.mark.asyncio
+ async def test_today_handler(self):
chat = Chat(gen_callback('Today'))
message = MessageHandler(chat, self.TEST_USER)
- assert message.process_callback() == "There're no events today."
+ assert await message.process_callback() == "There're no events today."
- def test_this_week_handler(self):
+ @pytest.mark.asyncio
+ async def test_this_week_handler(self):
chat = Chat(gen_callback('This week'))
message = MessageHandler(chat, self.TEST_USER)
- assert message.process_callback() == 'Choose a day.'
+ assert await message.process_callback() == 'Choose a day.'
- def test_chosen_day_handler(self):
+ @pytest.mark.asyncio
+ async def test_chosen_day_handler(self):
chat = Chat(gen_callback('10 Feb 2021'))
message = MessageHandler(chat, self.TEST_USER)
message.handlers['10 Feb 2021'] = message.chosen_day_handler
- assert (
- message.process_callback() == "There're no events on February 10.")
+ assert await message.process_callback() == \
+ "There're no events on February 10."
-def test_reply_unknown_user():
+@pytest.mark.asyncio
+async def test_reply_unknown_user():
chat = Chat(gen_message('/show_events'))
- answer = reply_unknown_user(chat)
+ answer = await reply_unknown_user(chat)
assert answer == '''
Hello, Moshe!
@@ -191,22 +200,28 @@ def test_reply_unknown_user():
class TestBotClient:
@staticmethod
- def test_telegram_router(profile_test_client):
- req = profile_test_client.get('/telegram')
- assert req.ok
- assert b"Start using PyLander telegram bot!" in req.content
+ @pytest.mark.asyncio
+ async def test_user_not_registered(telegram_client):
+ response = await telegram_client.post(
+ '/telegram/', json=gen_message('/start'))
+ assert response.status_code == status.HTTP_200_OK
+ assert b'Hello, Moshe!' in response.content
+ assert b'To use PyLander Bot you have to register' \
+ in response.content
@staticmethod
- def test_user_not_registered(telegram_client):
- req = telegram_client.post('/telegram/', json=gen_message('/start'))
- assert req.ok
- assert b'Hello, Moshe!' in req.content
- assert b'To use PyLander Bot you have to register' in req.content
-
- @staticmethod
- def test_user_registered(telegram_client, session):
+ @pytest.mark.asyncio
+ async def test_user_registered(telegram_client, session):
session.add(get_test_placeholder_user())
session.commit()
- req = telegram_client.post('/telegram/', json=gen_message('/start'))
- assert req.ok
- assert b'Welcome to Pylander telegram client!' in req.content
+ response = await telegram_client.post(
+ '/telegram/', json=gen_message('/start'))
+ assert response.status_code == status.HTTP_200_OK
+ assert b'Welcome to Pylander telegram client!' in response.content
+
+ @staticmethod
+ @pytest.mark.asyncio
+ async def test_telegram_router(telegram_client):
+ response = await telegram_client.get('/telegram')
+ assert response.status_code == status.HTTP_200_OK
+ assert b"Start using PyLander telegram bot!" in response.content
From 2122222c5501c5c0a6fa523e432a948cd41ac855 Mon Sep 17 00:00:00 2001
From: leddest <46251307+leddest@users.noreply.github.com>
Date: Tue, 26 Jan 2021 00:51:34 +0200
Subject: [PATCH 14/16] Add telegram tests
---
app/telegram/handlers.py | 20 ++++++++------
app/telegram/keyboards.py | 2 +-
tests/asyncio_fixture.py | 46 ++++++++++++++++++++++++++++++++
tests/test_a_telegram_asyncio.py | 37 +++++++++++++++++++++++--
4 files changed, 94 insertions(+), 11 deletions(-)
diff --git a/app/telegram/handlers.py b/app/telegram/handlers.py
index 5a30c191..c522418a 100644
--- a/app/telegram/handlers.py
+++ b/app/telegram/handlers.py
@@ -47,18 +47,20 @@ async def show_events_handler(self):
return answer
async def today_handler(self):
- today = datetime.date.today()
+ today = datetime.datetime.today()
events = [
- event for event in self.user.events
- if event.start <= today <= event.end]
+ _.events for _ in self.user.events
+ if _.events.start <= today <= _.events.end]
answer = f"{today.strftime('%B %d')}, {today.strftime('%A')} Events:\n"
- if not len(events):
+ if not events:
answer = "There're no events today."
for event in events:
- answer += f'\n\n{event.title}: from {event.start} to {event.ends}.'
+ answer += f'''
+From {event.start.strftime('%d/%m %H:%M')} \
+to {event.end.strftime('%d/%m %H:%M')}: {event.title}.\n'''
await telegram_bot.send_message(chat_id=self.chat.user_id, text=answer)
return answer
@@ -79,8 +81,8 @@ async def chosen_day_handler(self):
self.chat.message, DATE_FORMAT)
events = [
- event for event in self.user.events
- if event.start <= chosen_date <= event.end]
+ _.events for _ in self.user.events
+ if _.events.start <= chosen_date <= _.events.end]
answer = f"{chosen_date.strftime('%B %d')}, \
{chosen_date.strftime('%A')} Events:\n"
@@ -89,7 +91,9 @@ async def chosen_day_handler(self):
answer = f"There're no events on {chosen_date.strftime('%B %d')}."
for event in events:
- answer += f'\n\n{event.title}: from {event.start} to {event.ends}.'
+ answer += f'''
+From {event.start.strftime('%d/%m %H:%M')} \
+to {event.end.strftime('%d/%m %H:%M')}: {event.title}.\n'''
await telegram_bot.send_message(chat_id=self.chat.user_id, text=answer)
return answer
diff --git a/app/telegram/keyboards.py b/app/telegram/keyboards.py
index 6a0251e5..0cbdd66f 100644
--- a/app/telegram/keyboards.py
+++ b/app/telegram/keyboards.py
@@ -14,7 +14,7 @@
def get_this_week_buttons() -> List[List[Any]]:
- today = datetime.date.today()
+ today = datetime.datetime.today()
buttons = []
for day in range(1, 7):
day = today + datetime.timedelta(days=day)
diff --git a/tests/asyncio_fixture.py b/tests/asyncio_fixture.py
index a0b5b6d2..fbc79108 100644
--- a/tests/asyncio_fixture.py
+++ b/tests/asyncio_fixture.py
@@ -1,9 +1,13 @@
+from datetime import datetime, timedelta
+
from httpx import AsyncClient
import pytest
from app.database.database import Base
+from app.database.models import User
from app.main import app
from app.routers import telegram
+from app.routers.event import create_event
from tests.conftest import test_engine, get_test_db
@@ -15,3 +19,45 @@ async def telegram_client():
yield ac
app.dependency_overrides = {}
Base.metadata.drop_all(bind=test_engine)
+
+
+session = get_test_db()
+today_date = datetime.today().replace(hour=0, minute=0, second=0)
+
+
+def get_test_placeholder_user():
+ return User(
+ username='fake_user',
+ email='fake@mail.fake',
+ password='123456fake',
+ full_name='FakeName',
+ telegram_id='666666'
+ )
+
+
+@pytest.fixture
+def fake_user_events():
+ Base.metadata.create_all(bind=test_engine)
+ user = get_test_placeholder_user()
+ session.add(user)
+ session.commit()
+ create_event(
+ db=session,
+ title='Cool today event',
+ start=today_date,
+ end=today_date + timedelta(days=2),
+ content='test event',
+ owner_id=user.id,
+ location="Here",
+ )
+ create_event(
+ db=session,
+ title='Cool (somewhen in two days) event',
+ start=today_date + timedelta(days=1),
+ end=today_date + timedelta(days=3),
+ content='this week test event',
+ owner_id=user.id,
+ location="Here",
+ )
+ yield user
+ Base.metadata.drop_all(bind=test_engine)
diff --git a/tests/test_a_telegram_asyncio.py b/tests/test_a_telegram_asyncio.py
index 26b0c0bd..37d832ac 100644
--- a/tests/test_a_telegram_asyncio.py
+++ b/tests/test_a_telegram_asyncio.py
@@ -1,8 +1,12 @@
+from datetime import timedelta
+
from fastapi import status
import pytest
+from .asyncio_fixture import today_date
from .client_fixture import get_test_placeholder_user
from app.telegram.handlers import MessageHandler, reply_unknown_user
+from app.telegram.keyboards import DATE_FORMAT
from app.telegram.models import Bot, Chat
@@ -160,11 +164,22 @@ async def test_show_events_handler(self):
assert await message.process_callback() == 'Choose events day.'
@pytest.mark.asyncio
- async def test_today_handler(self):
+ async def test_no_today_events_handler(self):
chat = Chat(gen_callback('Today'))
message = MessageHandler(chat, self.TEST_USER)
assert await message.process_callback() == "There're no events today."
+ @pytest.mark.asyncio
+ async def test_today_handler(self, fake_user_events):
+ chat = Chat(gen_callback('Today'))
+ message = MessageHandler(chat, fake_user_events)
+ assert await message.process_callback() == f'''\
+{today_date.strftime('%B %d')}, {today_date.strftime('%A')} Events:
+
+From {today_date.strftime('%d/%m %H:%M')} \
+to {(today_date + timedelta(days=2)).strftime('%d/%m %H:%M')}: \
+Cool today event.\n'''
+
@pytest.mark.asyncio
async def test_this_week_handler(self):
chat = Chat(gen_callback('This week'))
@@ -172,13 +187,31 @@ async def test_this_week_handler(self):
assert await message.process_callback() == 'Choose a day.'
@pytest.mark.asyncio
- async def test_chosen_day_handler(self):
+ async def test_no_chosen_day_handler(self):
chat = Chat(gen_callback('10 Feb 2021'))
message = MessageHandler(chat, self.TEST_USER)
message.handlers['10 Feb 2021'] = message.chosen_day_handler
assert await message.process_callback() == \
"There're no events on February 10."
+ @pytest.mark.asyncio
+ async def test_chosen_day_handler(self, fake_user_events):
+ chosen_date = today_date + timedelta(days=2)
+ button = str(chosen_date.strftime(DATE_FORMAT))
+ chat = Chat(gen_callback(button))
+ message = MessageHandler(chat, fake_user_events)
+ message.handlers[button] = message.chosen_day_handler
+ assert await message.chosen_day_handler() == f'''\
+{chosen_date.strftime('%B %d')}, {chosen_date.strftime('%A')} Events:
+
+From {today_date.strftime('%d/%m %H:%M')} \
+to {(today_date + timedelta(days=2)).strftime('%d/%m %H:%M')}: \
+Cool today event.
+
+From {(chosen_date + timedelta(days=-1)).strftime('%d/%m %H:%M')} \
+to {(chosen_date + timedelta(days=1)).strftime('%d/%m %H:%M')}: \
+Cool (somewhen in two days) event.\n'''
+
@pytest.mark.asyncio
async def test_reply_unknown_user():
From c1d309a63f21f85cb9c885876de0f8d34754d9fa Mon Sep 17 00:00:00 2001
From: leddest <46251307+leddest@users.noreply.github.com>
Date: Wed, 27 Jan 2021 16:29:12 +0200
Subject: [PATCH 15/16] Make request.post works asynchronous
---
app/telegram/models.py | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/app/telegram/models.py b/app/telegram/models.py
index be6492da..b34b1a6e 100644
--- a/app/telegram/models.py
+++ b/app/telegram/models.py
@@ -1,5 +1,6 @@
from typing import Any, Dict, Optional
+from httpx import AsyncClient
import requests
@@ -47,10 +48,10 @@ async def send_message(
self, chat_id: str,
text: str,
reply_markup: Optional[Dict[str, Any]] = None):
- message = {
- 'chat_id': chat_id,
- 'text': text,
- 'reply_markup': None}
- if reply_markup:
- message.update(reply_markup)
- return requests.post(f'{self.base}sendMessage', data=message)
+ async with AsyncClient(base_url=self.base) as ac:
+ message = {
+ 'chat_id': chat_id,
+ 'text': text}
+ if reply_markup:
+ message.update(reply_markup)
+ return await ac.post('sendMessage', data=message)
From a5b4a6f6fedbea1134a5f3d803812125eb9af057 Mon Sep 17 00:00:00 2001
From: leddest <46251307+leddest@users.noreply.github.com>
Date: Wed, 27 Jan 2021 16:48:05 +0200
Subject: [PATCH 16/16] Minor change
---
tests/test_profile.py | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/tests/test_profile.py b/tests/test_profile.py
index 939705db..1b3a8d79 100644
--- a/tests/test_profile.py
+++ b/tests/test_profile.py
@@ -1,5 +1,6 @@
import os
+from fastapi import status
from PIL import Image
import pytest
@@ -31,7 +32,7 @@ def test_get_image_crop_area(width, height, result):
def test_profile_page(profile_test_client):
profile = profile_test_client.get('/profile')
data = profile.content
- assert profile.status_code == 200
+ assert profile.ok
assert b'profile.png' in data
assert b'FakeName' in data
assert b'Happy new user!' in data
@@ -47,7 +48,7 @@ def test_update_user_fullname(profile_test_client):
# Post new data
profile = profile_test_client.post(
'/profile/update_user_fullname', data=new_name_data)
- assert profile.status_code == 302
+ assert profile.status_code == status.HTTP_302_FOUND
# Get updated data
data = profile_test_client.get('/profile').content
@@ -64,7 +65,7 @@ def test_update_user_email(profile_test_client):
# Post new data
profile = profile_test_client.post(
'/profile/update_user_email', data=new_email)
- assert profile.status_code == 302
+ assert profile.status_code == status.HTTP_302_FOUND
# Get updated data
data = profile_test_client.get('/profile').content
@@ -81,7 +82,7 @@ def test_update_user_description(profile_test_client):
# Post new data
profile = profile_test_client.post(
'/profile/update_user_description', data=new_description)
- assert profile.status_code == 302
+ assert profile.status_code == status.HTTP_302_FOUND
# Get updated data
data = profile_test_client.get('/profile').content
@@ -98,7 +99,7 @@ def test_update_telegram_id(profile_test_client):
# Post new data
profile = profile_test_client.post(
'/profile/update_telegram_id', data=new_telegram_id)
- assert profile.status_code == 302
+ assert profile.status_code == status.HTTP_302_FOUND
# Get updated data
data = profile_test_client.get('/profile').content
@@ -116,7 +117,7 @@ def test_upload_user_photo(profile_test_client):
'/profile/upload_user_photo',
files={'file': (
"filename", open(example_new_photo, "rb"), "image/png")})
- assert profile.status_code == 302
+ assert profile.status_code == status.HTTP_302_FOUND
# Validate new picture saved in media directory
assert 'fake_user.png' in os.listdir(MEDIA_PATH)