Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access (auth) refactor & database transactions #46

Closed
wants to merge 52 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
7aef69d
feat: Switched heartbeat from POST to PUT method type
frgfm Nov 12, 2020
6c19302
feat: Switched location update route from POST to PUT type
frgfm Nov 12, 2020
5130624
refactor: Removed type casting
frgfm Nov 13, 2020
fa52620
test: Updated site unittests
frgfm Nov 13, 2020
4384407
test: Extended scope of unittests on sites
frgfm Nov 13, 2020
5453380
feat: Merged master
frgfm Nov 13, 2020
27f239d
style: Added extra typing
frgfm Nov 13, 2020
ed0968c
test: Refactored site unittests
frgfm Nov 13, 2020
cfa3fc0
test: Updated events' unittests
frgfm Nov 13, 2020
1ebb949
test: Fixed assert in login unittests
frgfm Nov 13, 2020
12a01e7
test: Added unittest for security
frgfm Nov 13, 2020
d178fa2
refactor: Renamed route function
frgfm Nov 13, 2020
78613ce
test: Fixed unittests naming
frgfm Nov 13, 2020
48b692b
test: Improved tests of events and sites
frgfm Nov 13, 2020
db2cc2b
test: Refactored unittests of accesses
frgfm Nov 13, 2020
8247ac0
style: Fixed lint
frgfm Nov 13, 2020
a2a66fd
style: Added extra typing
frgfm Nov 13, 2020
6298622
style: Lint change
frgfm Nov 13, 2020
9b27c1e
test: Refactored login route unittests
frgfm Nov 13, 2020
5b6bdaa
test: Updated alerts unittests
frgfm Nov 13, 2020
66cb5a1
test: Refactored media unittests
frgfm Nov 13, 2020
60e940e
test: Updated installation unittests
frgfm Nov 13, 2020
cc70003
refactor: Enforced password minimal length
frgfm Nov 13, 2020
2ad2126
style: Renamed password update route function
frgfm Nov 13, 2020
a0b5856
refactor: Refactored routing functions
frgfm Nov 13, 2020
ad4d0fb
refactor: Refactored routes
frgfm Nov 13, 2020
686a15b
test: Refactored users unittests
frgfm Nov 14, 2020
6fb9b21
style: Renamed mock tables
frgfm Nov 14, 2020
f3b8f1b
style: Renamed monkeypatch
frgfm Nov 14, 2020
25e21b1
fix: Fixed query filter
frgfm Nov 14, 2020
5d33242
style: Cleaned routes
frgfm Nov 14, 2020
7a5154e
test: Refactored device unittests
frgfm Nov 14, 2020
c39dfd9
test: Fixed mock table deepcopies
frgfm Nov 14, 2020
b6ac23b
docs: Added comments
frgfm Nov 14, 2020
0b7637f
refactor: Removed unused schema
frgfm Nov 14, 2020
b5bfac1
test: Fixed lint
frgfm Nov 14, 2020
1c81ea9
test: Fixed tests
frgfm Nov 14, 2020
105f608
style: Added typing
frgfm Nov 14, 2020
5c4280d
test: Refactored pytest fixtures
frgfm Nov 14, 2020
1521dc7
refactor: Removed unused imports
frgfm Nov 14, 2020
1bfff57
test: Fixed mock crud
frgfm Nov 14, 2020
a76f531
refactor: Moved all routing functions to crud
frgfm Nov 14, 2020
ab8104c
refactor: Removed unused import
frgfm Nov 14, 2020
3b6127c
test: Fixed assert error message
frgfm Nov 14, 2020
a73533c
style: Specified arguments
frgfm Nov 15, 2020
2b279a8
refactor: Refactored schemas
frgfm Nov 15, 2020
6f10141
test: Reduced patching
frgfm Nov 15, 2020
1476822
refactor: crud module split in submodules base & accesses
Nov 18, 2020
edc8a13
tests: update _patch_session to mock crud.base
Nov 18, 2020
3d14b75
feat: write accesses using db transactions
Nov 18, 2020
be8b88d
feat: db users/devices on delete cascade the access
Nov 18, 2020
23348ed
Merge branch 'master' into access-refacto
Nov 19, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/app/api/crud/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .base import *
37 changes: 37 additions & 0 deletions src/app/api/crud/accesses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from typing import Dict, Any
from fastapi import HTTPException
from app.api import crud, security
from app.db import accesses
from app.api.schemas import AccessRead, AccessCreation, Cred, AccessBase, CredHash


async def post_access(login: str, password: str, scopes: str) -> AccessRead:
"""Assert login does not exist, hash password, post entry into accesses table"""
if await crud.fetch_one(accesses, [('login', login)]) is not None:
raise HTTPException(
status_code=400,
detail=f"An entry with login='{login}' already exists.",
)

pwd = await security.hash_password(password)
access = AccessCreation(login=login, hashed_password=pwd, scopes=scopes)
entry = await crud.create_entry(accesses, access)
return AccessRead(**entry)


async def update_access_login(payload: AccessBase, entry_id: int) -> Dict[str, Any]:
# TODO check that works
entry = await crud.update_entry(accesses, payload, entry_id)
# Return non-sensitive information
return {"login": entry["login"]}


async def update_access_pwd(payload: Cred, entry_id: int) -> Dict[str, Any]:
"""hash new password & update entry"""
pwd = await security.hash_password(payload.password)

updated_payload = CredHash(hashed_password=pwd)
entry = await crud.update_entry(accesses, updated_payload, entry_id)

# Return non-sensitive information
return {"login": entry["login"]}
3 changes: 2 additions & 1 deletion src/app/api/crud.py → src/app/api/crud/base.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from typing import Optional, Tuple, Any, List, Dict
from app.db import database
from sqlalchemy import Table
from pydantic import BaseModel
from fastapi import HTTPException, Path

from app.db import database


async def post(payload: BaseModel, table: Table) -> int:
query = table.insert().values(**payload.dict())
Expand Down
51 changes: 6 additions & 45 deletions src/app/api/routes/accesses.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,15 @@
from typing import List, Dict, Any
from fastapi import APIRouter, Path, HTTPException
from app.api import crud, security
"""Access route read-only for debugging purpose, aims to be removed"""

from typing import List
from fastapi import APIRouter, Path
from app.api import crud
from app.db import accesses
from app.api.schemas import AccessBase, AccessRead, AccessAuth, AccessCreation, Cred, CredHash
from app.api.schemas import AccessRead


router = APIRouter()


async def post_access(login: str, password: str, scopes: str) -> AccessRead:
# Check that the login does not already exist
if await crud.fetch_one(accesses, [('login', login)]) is not None:
raise HTTPException(
status_code=400,
detail=f"An entry with login='{login}' already exists.",
)
# Hash the password
pwd = await security.hash_password(password)
access = AccessCreation(login=login, hashed_password=pwd, scopes=scopes)
entry = await crud.create_entry(accesses, access)

return AccessRead(**entry)


async def update_access_pwd(payload: Cred, entry_id: int = Path(..., gt=0)) -> Dict[str, Any]:
entry = await crud.get_entry(accesses, entry_id)
# Hash the password
pwd = await security.hash_password(payload.password)
# Update the password
payload = CredHash(hashed_password=pwd)
await crud.update_entry(accesses, payload, entry_id)
# Return non-sensitive information
return {"login": entry["login"]}


@router.post("/", response_model=AccessRead, status_code=201)
async def create_access(payload: AccessAuth):
return await post_access(payload.login, payload.password, payload.scopes)


@router.get("/{access_id}/", response_model=AccessRead)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to remove the access routes (it is fine by me if we think we don't need it anymore for debugging purpose), why leave those routes?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think he mentioned that we don't want to expose creation/modification of accesses but reading is fine

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes reading is fine while it does not break database consistency

async def get_access(access_id: int = Path(..., gt=0)):
return await crud.get_entry(accesses, access_id)
Expand All @@ -47,13 +18,3 @@ async def get_access(access_id: int = Path(..., gt=0)):
@router.get("/", response_model=List[AccessRead])
async def fetch_accesses():
return await crud.fetch_all(accesses)


@router.put("/{access_id}/", response_model=AccessRead)
async def update_access(payload: AccessBase, access_id: int = Path(..., gt=0)):
return await crud.update_entry(accesses, payload, access_id)


@router.delete("/{access_id}/", response_model=AccessRead)
async def delete_access(access_id: int = Path(..., gt=0)):
return await crud.delete_entry(accesses, access_id)
7 changes: 5 additions & 2 deletions src/app/api/routes/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
from fastapi import APIRouter, Path, Security, HTTPException

from app.api import crud
from app.db import devices
from app.db import database, devices
from app.api.schemas import DeviceOut, DeviceAuth, DeviceCreation, DeviceIn, UserRead, DefaultPosition, Cred
from app.api.deps import get_current_device, get_current_user

from app.api.routes.accesses import post_access, update_access_pwd
from app.api.crud.accesses import post_access, update_access_pwd


router = APIRouter()


@router.post("/", response_model=DeviceOut, status_code=201)
@database.transaction()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why add this decorator only for route that create objects?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I planned to add the decorator when transaction is needed (writing in several tables with atomicity), user/device creation was the first use case, did I miss other ones ?

I realised that route decorator was not the best practice: it makes it impossible to test. We may need to find a way to mock a database transaction or we'll have to rewrite operations into pytest mocks. @frgfm do you see what I mean ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am no where close to be knowledgeable about database transactions since I just discovered that thanks to you @jeanpasquier75. What is the expected behaviour difference after decorating the route? reducing execution time? output type change?

If it's beneficial to the API, even if we can't test it per se, I would argue that we should still put it. If it doesn't bring much and we can't test it, then perhaps let's leave it out

async def create_device(payload: DeviceAuth, _=Security(get_current_user, scopes=["admin"])):
"""Use transaction to insert access/device."""
access_entry = await post_access(payload.login, payload.password, scopes=payload.scopes)
return await crud.create_entry(devices, DeviceCreation(**payload.dict(), access_id=access_entry.id))

Expand Down
11 changes: 5 additions & 6 deletions src/app/api/routes/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
from fastapi import APIRouter, Path, Security

from app.api import crud
from app.db import users
from app.db import database, users
from app.api.schemas import UserInfo, UserCreation, Cred, UserRead, UserAuth
from app.api.deps import get_current_user

from app.api.routes.accesses import post_access, update_access_pwd


router = APIRouter()

Expand All @@ -26,14 +24,15 @@ async def update_my_info(payload: UserInfo, me: UserRead = Security(get_current_
@router.put("/update-pwd", response_model=UserInfo)
async def update_my_password(payload: Cred, me: UserRead = Security(get_current_user, scopes=["me"])):
entry = await crud.get_entry(users, me.id)
await update_access_pwd(payload, entry["access_id"])
await crud.update_access_pwd(payload, entry["access_id"])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You forgot to do the same for the admin version further down below ;)

return entry


@router.post("/", response_model=UserRead, status_code=201)
@database.transaction()
frgfm marked this conversation as resolved.
Show resolved Hide resolved
async def create_user(payload: UserAuth, _=Security(get_current_user, scopes=["admin"])):
# Create a new access
access_entry = await post_access(payload.login, payload.password, payload.scopes)
"""Use transaction to insert access/user."""
access_entry = await crud.accesses.post_access(payload.login, payload.password, payload.scopes)
return await crud.create_entry(users, UserCreation(login=payload.login, access_id=access_entry.id))


Expand Down
4 changes: 2 additions & 2 deletions src/app/db/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"accesses",
metadata,
Column("id", Integer, primary_key=True),
Column("login", String(50)),
Column("login", String(50), index=True, unique=True), # index for fast lookup
frgfm marked this conversation as resolved.
Show resolved Hide resolved
Column("hashed_password", String(70), nullable=False),
Column("scopes", String(30), default="me", nullable=False),
)
Expand Down Expand Up @@ -75,7 +75,7 @@ class EventType(str, enum.Enum):
Column("id", Integer, primary_key=True),
Column("login", String(50)),
Column("owner_id", Integer, ForeignKey("users.id")),
Column("access_id", Integer, ForeignKey("accesses.id"), unique=True),
Column("access_id", Integer, ForeignKey("accesses.id", ondelete="CASCADE"), unique=True),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So deleting the device will delete the access? If so, shouldn't we do the same for the user?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And shall we do it also for the owner_id ? If we delete the users, the devices will disappear?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I thought that I also had added the on delete cascade on user but I forgot.

I figured out that it is the opposite: deleting the access deletes the device/user. I am working on fixing the device/user deletion.

I will add the mechanism owner id as well !

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another issue when we use the on cascade mechanism -> as it perform at db level we have to find a way to mock it in our unit tests

Column("specs", String(50)),
Column("elevation", Float(1, asdecimal=True), default=None, nullable=True),
Column("lat", Float(4, asdecimal=True), default=None, nullable=True),
Expand Down
109 changes: 4 additions & 105 deletions src/tests/test_accesses.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import json
import pytest
from copy import deepcopy

from app.api import crud
from app.api.crud import base
from app.api.routes import accesses


Expand All @@ -16,12 +15,9 @@ def _patch_session(monkeypatch, mock_table):
# DB patching
monkeypatch.setattr(accesses, "accesses", mock_table)
# Sterilize all DB interactions through CRUD override
monkeypatch.setattr(crud, "get", pytest.mock_get)
monkeypatch.setattr(crud, "fetch_all", pytest.mock_fetch_all)
monkeypatch.setattr(crud, "fetch_one", pytest.mock_fetch_one)
monkeypatch.setattr(crud, "post", pytest.mock_post)
monkeypatch.setattr(crud, "put", pytest.mock_put)
monkeypatch.setattr(crud, "delete", pytest.mock_delete)
monkeypatch.setattr(base, "get", pytest.mock_get)
monkeypatch.setattr(base, "fetch_all", pytest.mock_fetch_all)
monkeypatch.setattr(base, "fetch_one", pytest.mock_fetch_one)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you made the crud init, you can keep the previous version (you have to patch the thin imported in the route, and does not import base, but crud)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it is not that simple (I think, I am not masting import mechanisms) because it seams that mocking crud.get will keep untouched the original crud.base.get function and if it imported afterward the original is used instead of the mocked version. So far the only way I could mock all crud function was to mock both crud._ and crud.base._ (_ beeing each function). Do you see ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmmh, what error did you get with my suggestion?

Because the import for the accesses is over here: https://github.com/pyronear/pyro-api/blob/master/src/app/api/routes/accesses.py#L3

So if we patch this very import, it should be working without any problem 🤔



def test_get_access(test_app, monkeypatch):
Expand Down Expand Up @@ -62,100 +58,3 @@ def test_fetch_accesses(test_app, monkeypatch):
assert response.status_code == 200
assert response.json() == [{k: v for k, v in entry.items() if k != "hashed_password"}
for entry in mock_access_table]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we remove the unittests for the routes, we need to adapt them to test the crud.accesses functions. I don't think it would be wise to decrease coverage by that much



def test_create_access(test_app, monkeypatch):

# Sterilize DB interactions
mock_access_table = deepcopy(ACCESS_TABLE)
_patch_session(monkeypatch, mock_access_table)

test_payload = {"login": "third_login", "scopes": "me", "password": "PickARobustOne"}
test_response = {"id": len(mock_access_table) + 1, **test_payload}

response = test_app.post("/accesses/", data=json.dumps(test_payload))

assert response.status_code == 201
assert response.json() == {k: v for k, v in test_response.items() if k != "password"}


@pytest.mark.parametrize(
"payload, status_code, status_details",
[
[{"login": "first_login", "password": "PickARobustOne", "scopes": "me"}, 400,
"An entry with login='first_login' already exists."],
[{"login": "third_login", "scopes": "me", "hashed_password": "PickARobustOne"}, 422, None],
[{"login": 1, "scopes": "me", "password": "PickARobustOne"}, 422, None],
[{"login": "third_login", "scopes": "me"}, 422, None],
],
)
def test_create_access_invalid(test_app, monkeypatch, payload, status_code, status_details):
# Sterilize DB interactions
mock_access_table = deepcopy(ACCESS_TABLE)
_patch_session(monkeypatch, mock_access_table)

response = test_app.post("/accesses/", data=json.dumps(payload))
assert response.status_code == status_code, print(payload)
if isinstance(status_details, str):
assert response.json()["detail"] == status_details, print(payload)


def test_update_access(test_app, monkeypatch):
# Sterilize DB interactions
mock_access_table = deepcopy(ACCESS_TABLE)
_patch_session(monkeypatch, mock_access_table)

test_payload = {"login": "first_login", "scopes": "me", "password": "PickAnotherRobustOne"}
response = test_app.put("/accesses/1/", data=json.dumps(test_payload))
assert response.status_code == 200
for k, v in mock_access_table[0].items():
assert v == test_payload.get(k, ACCESS_TABLE[0][k])


@pytest.mark.parametrize(
"access_id, payload, status_code",
[
[1, {}, 422],
[1, {"login": "first_login"}, 422],
[999, {"login": "first_login", "scopes": "me", "password": "PickAnotherRobustOne"}, 404],
[1, {"login": 1, "scopes": "me", "password": "PickAnotherRobustOne"}, 422],
[0, {"login": "first_login", "scopes": "me", "password": "PickAnotherRobustOne"}, 422],
],
)
def test_update_access_invalid(test_app, monkeypatch, access_id, payload, status_code):
# Sterilize DB interactions
mock_access_table = deepcopy(ACCESS_TABLE)
_patch_session(monkeypatch, mock_access_table)

response = test_app.put(f"/accesses/{access_id}/", data=json.dumps(payload))
assert response.status_code == status_code, print(payload)


def test_delete_access(test_app, monkeypatch):
# Sterilize DB interactions
mock_access_table = deepcopy(ACCESS_TABLE)
_patch_session(monkeypatch, mock_access_table)

response = test_app.delete("/accesses/1/")
assert response.status_code == 200
assert response.json() == {k: v for k, v in ACCESS_TABLE[0].items() if k != "hashed_password"}
for entry in mock_access_table:
assert entry['id'] != 1


@pytest.mark.parametrize(
"access_id, status_code, status_details",
[
[999, 404, "Entry not found"],
[0, 422, None],
],
)
def test_delete_access_invalid(test_app, monkeypatch, access_id, status_code, status_details):
# Sterilize DB interactions
mock_access_table = deepcopy(ACCESS_TABLE)
_patch_session(monkeypatch, mock_access_table)

response = test_app.delete(f"/accesses/{access_id}/")
assert response.status_code == status_code, print(access_id)
if isinstance(status_details, str):
assert response.json()["detail"] == status_details, print(access_id)
12 changes: 6 additions & 6 deletions src/tests/test_alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from copy import deepcopy
from datetime import datetime

from app.api import crud
from app.api.crud import base
from app.api.routes import alerts


Expand All @@ -21,11 +21,11 @@ def _patch_session(monkeypatch, mock_table):
# DB patching
monkeypatch.setattr(alerts, "alerts", mock_table)
# Sterilize all DB interactions through CRUD override
monkeypatch.setattr(crud, "get", pytest.mock_get)
monkeypatch.setattr(crud, "fetch_all", pytest.mock_fetch_all)
monkeypatch.setattr(crud, "post", pytest.mock_post)
monkeypatch.setattr(crud, "put", pytest.mock_put)
monkeypatch.setattr(crud, "delete", pytest.mock_delete)
monkeypatch.setattr(base, "get", pytest.mock_get)
monkeypatch.setattr(base, "fetch_all", pytest.mock_fetch_all)
monkeypatch.setattr(base, "post", pytest.mock_post)
monkeypatch.setattr(base, "put", pytest.mock_put)
monkeypatch.setattr(base, "delete", pytest.mock_delete)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here



def test_get_alert(test_app, monkeypatch):
Expand Down
15 changes: 8 additions & 7 deletions src/tests/test_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from datetime import datetime
from fastapi import HTTPException

from app.api import crud, security
from app.api import security
from app.api.crud import base
from app.api.routes import devices
from app.api.schemas import AccessRead, AccessCreation

Expand All @@ -30,12 +31,12 @@ def _patch_session(monkeypatch, mock_device_table, mock_access_table=None):
# DB patching
monkeypatch.setattr(devices, "devices", mock_device_table)
# Sterilize all DB interactions through CRUD override
monkeypatch.setattr(crud, "get", pytest.mock_get)
monkeypatch.setattr(crud, "fetch_all", pytest.mock_fetch_all)
monkeypatch.setattr(crud, "fetch_one", pytest.mock_fetch_one)
monkeypatch.setattr(crud, "post", pytest.mock_post)
monkeypatch.setattr(crud, "put", pytest.mock_put)
monkeypatch.setattr(crud, "delete", pytest.mock_delete)
monkeypatch.setattr(base, "get", pytest.mock_get)
monkeypatch.setattr(base, "fetch_all", pytest.mock_fetch_all)
monkeypatch.setattr(base, "fetch_one", pytest.mock_fetch_one)
monkeypatch.setattr(base, "post", pytest.mock_post)
monkeypatch.setattr(base, "put", pytest.mock_put)
monkeypatch.setattr(base, "delete", pytest.mock_delete)
# Password
monkeypatch.setattr(security, "hash_password", pytest.mock_hash_password)
# Access table specific
Expand Down
12 changes: 6 additions & 6 deletions src/tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from copy import deepcopy
from datetime import datetime

from app.api import crud
from app.api.crud import base
from app.api.routes import events


Expand All @@ -19,11 +19,11 @@ def _patch_session(monkeypatch, mock_table):
# DB patching
monkeypatch.setattr(events, "events", mock_table)
# Sterilize all DB interactions through CRUD override
monkeypatch.setattr(crud, "get", pytest.mock_get)
monkeypatch.setattr(crud, "fetch_all", pytest.mock_fetch_all)
monkeypatch.setattr(crud, "post", pytest.mock_post)
monkeypatch.setattr(crud, "put", pytest.mock_put)
monkeypatch.setattr(crud, "delete", pytest.mock_delete)
monkeypatch.setattr(base, "get", pytest.mock_get)
monkeypatch.setattr(base, "fetch_all", pytest.mock_fetch_all)
monkeypatch.setattr(base, "post", pytest.mock_post)
monkeypatch.setattr(base, "put", pytest.mock_put)
monkeypatch.setattr(base, "delete", pytest.mock_delete)


def test_get_event(test_app, monkeypatch):
Expand Down
Loading