Skip to content

Commit

Permalink
Merge pull request #16 from caarmen/add-db-tests
Browse files Browse the repository at this point in the history
Add a full test of the withings notification webhook
  • Loading branch information
caarmen authored Jun 14, 2023
2 parents 3f189d4 + a927d5d commit 694e4f9
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 5 deletions.
9 changes: 7 additions & 2 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
-r prod.txt
ruff==0.0.263
black==23.3.0
factory-boy==3.2.1
httpx==0.24.1
isort==5.12.0
pytest==7.3.1
pytest-cov==4.0.0
pytest-factoryboy==2.5.1
pytest-sqlalchemy-mock==0.1.5
pytest==7.3.1
requests-mock==1.11.0
ruff==0.0.263
2 changes: 1 addition & 1 deletion scripts/codecheck.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ for project in slackhealthbot alembic tests
do
black $project
ruff check $project
isort $project
isort --profile black $project
done
3 changes: 1 addition & 2 deletions slackhealthbot/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
from typing import Optional

from sqlalchemy import Float, ForeignKey, String, func
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm import Mapped, declarative_base, mapped_column, relationship

Base = declarative_base()

Expand Down
25 changes: 25 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import pytest
from pytest_factoryboy import register

from slackhealthbot.database.models import Base
from tests.factories.factories import (
FitbitUserFactory,
UserFactory,
WithingsUserFactory,
)


@pytest.fixture(scope="function")
def sqlalchemy_declarative_base():
return Base


@pytest.fixture(scope="function", autouse=True)
def setup_factories(mocked_session):
for factory in [UserFactory, WithingsUserFactory, FitbitUserFactory]:
factory._meta.sqlalchemy_session = mocked_session
factory._meta.sqlalchemy_session_persistence = "commit"


for factory in [UserFactory, WithingsUserFactory, FitbitUserFactory]:
register(factory)
Empty file added tests/factories/__init__.py
Empty file.
37 changes: 37 additions & 0 deletions tests/factories/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from factory import Faker, SelfAttribute, SubFactory
from factory.alchemy import SQLAlchemyModelFactory

from slackhealthbot.database.models import FitbitUser, User, WithingsUser


class WithingsUserFactory(SQLAlchemyModelFactory):
class Meta:
model = WithingsUser

user_id = Faker("pyint")
oauth_access_token = Faker("pystr")
oauth_refresh_token = Faker("pystr")
oauth_userid = Faker("pystr")
oauth_expiration_date = Faker("date_time")
last_weight = Faker("pyfloat")


class FitbitUserFactory(SQLAlchemyModelFactory):
class Meta:
model = FitbitUser

user_id = Faker("pyint")
oauth_access_token = Faker("pystr")
oauth_refresh_token = Faker("pystr")
oauth_userid = Faker("pystr")
oauth_expiration_date = Faker("date_time")


class UserFactory(SQLAlchemyModelFactory):
class Meta:
model = User

id = Faker("pyint")
slack_alias = Faker("pystr")
withings = SubFactory(WithingsUserFactory, user_id=SelfAttribute("..id"))
fitbit = SubFactory(FitbitUserFactory, user_id=SelfAttribute("..id"))
102 changes: 102 additions & 0 deletions tests/routes/test_withings_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import datetime
import json
import math

import pytest
from requests_mock.mocker import Mocker

from slackhealthbot.database import crud
from slackhealthbot.database.models import User, WithingsUser
from slackhealthbot.main import withings_notification_webhook
from slackhealthbot.settings import settings
from tests.factories.factories import UserFactory, WithingsUserFactory


@pytest.mark.parametrize(
argnames="input_initial_weight, "
"input_new_weight_g, "
"expected_new_latest_weight_kg, "
"expected_icon",
argvalues=[
(None, 52100, 52.1, ""),
(52.1, 52200, 52.2, "↗️"),
(52.1, 53200, 53.2, "⬆️"),
(53.1, 53000, 53.0, "↘️"),
(53.0, 51900, 51.9, "⬇️"),
(52.3, 52300, 52.3, "➡️"),
],
)
def test_first_user_weight(
mocked_session,
requests_mock: Mocker,
user_factory: UserFactory,
withings_user_factory: WithingsUserFactory,
input_initial_weight,
input_new_weight_g,
expected_new_latest_weight_kg,
expected_icon,
):
"""
Given a user with a given previous weight logged
When we receive the callback from withings that a new weight is available
Then the last_weight is updated in the database
And the message is posted to slack with the correct icon.
"""

# Given a user
user: User = user_factory(withings=None)
withings_user: WithingsUser = withings_user_factory(
user_id=user.id,
last_weight=input_initial_weight,
oauth_expiration_date=datetime.datetime.utcnow() + datetime.timedelta(days=1),
)
db_user = crud.get_user(
mocked_session, withings_oauth_userid=withings_user.oauth_userid
)
db_withings_user = db_user.withings
# The user has no previous weight logged
assert db_withings_user.last_weight == input_initial_weight

# Mock withings endpoint to return some weight data
requests_mock.post(
url=f"{settings.withings_base_url}measure",
json={
"status": 0,
"body": {
"measuregrps": [
{
"measures": [
{
"value": input_new_weight_g,
"unit": -3,
}
],
},
],
},
},
)

# Verify we call the slack post with the expected request data
def slack_post_matcher(request):
request_data = json.loads(request.text)
assert expected_icon in request_data["text"]
return True

# Mock an empty ok response from the slack webhook
requests_mock.post(
url=f"{settings.slack_webhook_url}",
status_code=200,
additional_matcher=slack_post_matcher,
)

# When we receive the callback from withings that a new weight is available
withings_notification_webhook(
userid=withings_user.oauth_userid,
startdate=1683894606,
enddate=1686570821,
db=mocked_session,
)

# Then the last_weight is updated in the database
assert math.isclose(db_user.withings.last_weight, expected_new_latest_weight_kg)
Empty file.
75 changes: 75 additions & 0 deletions tests/test_factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from datetime import datetime

from slackhealthbot.database import crud
from slackhealthbot.database.models import FitbitUser, User, WithingsUser
from tests.factories.factories import (
FitbitUserFactory,
UserFactory,
WithingsUserFactory,
)


def test_user_factory(
user_factory: UserFactory,
mocked_session,
):
user: User = user_factory()
assert isinstance(user.slack_alias, str)
assert isinstance(user.id, int)
assert isinstance(user.withings, WithingsUser)
assert isinstance(user.fitbit, FitbitUser)
db_user = crud.get_user(mocked_session, slack_alias=user.slack_alias)
assert user.id == db_user.id
assert user.slack_alias == db_user.slack_alias
assert user.withings.id == db_user.withings.id
assert user.fitbit.id == db_user.fitbit.id


def test_withings_user_factory(
user_factory: UserFactory,
withings_user_factory: WithingsUserFactory,
mocked_session,
):
user: User = user_factory(withings=None)
withings_user: WithingsUser = withings_user_factory(user_id=user.id)
assert isinstance(withings_user.oauth_access_token, str)
assert isinstance(withings_user.oauth_refresh_token, str)
assert isinstance(withings_user.oauth_userid, str)
assert isinstance(withings_user.oauth_expiration_date, datetime)
assert isinstance(withings_user.last_weight, float)
assert isinstance(user, User)

db_user = crud.get_user(
mocked_session, withings_oauth_userid=withings_user.oauth_userid
)
db_withings_user = db_user.withings
assert db_withings_user.oauth_access_token == withings_user.oauth_access_token
assert db_withings_user.oauth_refresh_token == withings_user.oauth_refresh_token
assert db_withings_user.oauth_userid == withings_user.oauth_userid
assert db_withings_user.oauth_expiration_date == withings_user.oauth_expiration_date
assert db_withings_user.last_weight == withings_user.last_weight
assert db_withings_user.user.id == user.id


def test_fitbit_user_factory(
user_factory: UserFactory,
fitbit_user_factory: FitbitUserFactory,
mocked_session,
):
user: User = user_factory(fitbit=None)
fitbit_user: FitbitUser = fitbit_user_factory(user_id=user.id)
assert isinstance(fitbit_user.oauth_access_token, str)
assert isinstance(fitbit_user.oauth_refresh_token, str)
assert isinstance(fitbit_user.oauth_userid, str)
assert isinstance(fitbit_user.oauth_expiration_date, datetime)
assert isinstance(user, User)

db_user = crud.get_user(
mocked_session, fitbit_oauth_userid=fitbit_user.oauth_userid
)
db_fitbit_user = db_user.fitbit
assert db_fitbit_user.oauth_access_token == fitbit_user.oauth_access_token
assert db_fitbit_user.oauth_refresh_token == fitbit_user.oauth_refresh_token
assert db_fitbit_user.oauth_userid == fitbit_user.oauth_userid
assert db_fitbit_user.oauth_expiration_date == fitbit_user.oauth_expiration_date
assert db_fitbit_user.user.id == user.id

0 comments on commit 694e4f9

Please sign in to comment.