diff --git a/.gitignore b/.gitignore index 9fe17bc..b6e4761 100644 --- a/.gitignore +++ b/.gitignore @@ -126,4 +126,4 @@ venv.bak/ dmypy.json # Pyre type checker -.pyre/ \ No newline at end of file +.pyre/ diff --git a/migrations/versions/670f4caac7dd_init.py b/migrations/versions/670f4caac7dd_init.py index 3f30c54..9a23814 100644 --- a/migrations/versions/670f4caac7dd_init.py +++ b/migrations/versions/670f4caac7dd_init.py @@ -8,19 +8,24 @@ def upgrade(): - op.create_table('category', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('type', sa.String(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + 'category', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('type', sa.String(), nullable=True), + sa.PrimaryKeyConstraint('id'), ) - op.create_table('button', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('category_id', sa.Integer(), nullable=True), - sa.Column('icon', sa.String(), nullable=True), - sa.ForeignKeyConstraint(['category_id'], ['category.id'], ), - sa.PrimaryKeyConstraint('id') + op.create_table( + 'button', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('category_id', sa.Integer(), nullable=True), + sa.Column('icon', sa.String(), nullable=True), + sa.ForeignKeyConstraint( + ['category_id'], + ['category.id'], + ), + sa.PrimaryKeyConstraint('id'), ) diff --git a/migrations/versions/6a486347af93_order.py b/migrations/versions/6a486347af93_order.py new file mode 100644 index 0000000..15bb287 --- /dev/null +++ b/migrations/versions/6a486347af93_order.py @@ -0,0 +1,42 @@ +"""order + +Revision ID: 6a486347af93 +Revises: 670f4caac7dd +Create Date: 2023-02-11 10:18:11.179485 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '6a486347af93' +down_revision = '670f4caac7dd' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('button', sa.Column('order', sa.Integer(), nullable=False)) + op.add_column('button', sa.Column('link', sa.String(), nullable=False)) + op.add_column('button', sa.Column('type', sa.String(), nullable=False)) + op.alter_column('button', 'name', existing_type=sa.VARCHAR(), nullable=False) + op.alter_column('button', 'category_id', existing_type=sa.INTEGER(), nullable=False) + op.alter_column('button', 'icon', existing_type=sa.VARCHAR(), nullable=False) + op.add_column('category', sa.Column('order', sa.Integer(), nullable=False)) + op.alter_column('category', 'name', existing_type=sa.VARCHAR(), nullable=False) + op.alter_column('category', 'type', existing_type=sa.VARCHAR(), nullable=False) + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('category', 'type', existing_type=sa.VARCHAR(), nullable=True) + op.alter_column('category', 'name', existing_type=sa.VARCHAR(), nullable=True) + op.drop_column('category', 'order') + op.alter_column('button', 'icon', existing_type=sa.VARCHAR(), nullable=True) + op.alter_column('button', 'category_id', existing_type=sa.INTEGER(), nullable=True) + op.alter_column('button', 'name', existing_type=sa.VARCHAR(), nullable=True) + op.drop_column('button', 'type') + op.drop_column('button', 'link') + op.drop_column('button', 'order') + # ### end Alembic commands ### diff --git a/services_backend/models/database.py b/services_backend/models/database.py index 31c0685..7804df1 100644 --- a/services_backend/models/database.py +++ b/services_backend/models/database.py @@ -1,18 +1,23 @@ -from sqlalchemy import Column, Integer, String, ForeignKey -from sqlalchemy.orm import relationship +from __future__ import annotations +from sqlalchemy import Integer, String, ForeignKey +from sqlalchemy.orm import relationship, Mapped, mapped_column from .base import Base class Category(Base): - id = Column(Integer, primary_key=True) - name = Column(String) - type = Column(String) - buttons = relationship("Button", back_populates="category", foreign_keys="Button.category_id") + id: Mapped[int] = mapped_column(Integer, primary_key=True) + order: Mapped[int] = mapped_column(Integer, default=1) + name: Mapped[str] = mapped_column(String) + type: Mapped[str] = mapped_column(String) + buttons: Mapped[list[Button]] = relationship("Button", back_populates="category", foreign_keys="Button.category_id") class Button(Base): - id = Column(Integer, primary_key=True) - name = Column(String) - category_id = Column(Integer, ForeignKey(Category.id)) - category = relationship("Category", back_populates="buttons", foreign_keys=[category_id]) - icon = Column(String) + id: Mapped[int] = mapped_column(Integer, primary_key=True) + name: Mapped[str] = mapped_column(String) + order: Mapped[int] = mapped_column(Integer, default=1) + category_id: Mapped[int] = mapped_column(Integer, ForeignKey(Category.id)) + category: Mapped[Category] = relationship("Category", back_populates="buttons", foreign_keys=[category_id]) + icon: Mapped[str] = mapped_column(String) + link: Mapped[str] = mapped_column(String) + type: Mapped[str] = mapped_column(String) diff --git a/services_backend/routes/base.py b/services_backend/routes/base.py index 61a4d44..bfeb314 100644 --- a/services_backend/routes/base.py +++ b/services_backend/routes/base.py @@ -10,9 +10,7 @@ app = FastAPI() -app.add_middleware( - DBSessionMiddleware, db_url=settings.DB_DSN, session_args={"autocommit": True}, engine_args={"pool_pre_ping": True} -) +app.add_middleware(DBSessionMiddleware, db_url=settings.DB_DSN, engine_args={"pool_pre_ping": True}) app.add_middleware( CORSMiddleware, @@ -22,5 +20,5 @@ allow_headers=settings.CORS_ALLOW_HEADERS, ) -app.include_router(button, prefix='/button', tags=["Button"]) +app.include_router(button, prefix='/category/{category_id}/button', tags=["Button"]) app.include_router(category, prefix='/category', tags=["Category"]) diff --git a/services_backend/routes/button.py b/services_backend/routes/button.py index e4955e1..0ae46fe 100644 --- a/services_backend/routes/button.py +++ b/services_backend/routes/button.py @@ -2,54 +2,94 @@ from fastapi_sqlalchemy import db from .models.button import ButtonCreate, ButtonUpdate, ButtonGet +from .models.category import CategoryGet from ..models.database import Button, Category button = APIRouter() @button.post("/", response_model=ButtonGet) -def create_button(button_inp: ButtonCreate): - category = db.session.query(Category).filter(Category.id == button_inp.category_id).one_or_none() +def create_button(button_inp: ButtonCreate, category_id: int): + category = db.session.query(Category).filter(Category.id == category_id).one_or_none() if not category: raise HTTPException(status_code=404, detail="Category does not exist") - button = Button(**button_inp.dict()) + last_button = db.session.query(Button).order_by(Button.order.desc()).first() + button = Button(**button_inp.dict(exclude_none=True)) + button.category_id = category_id + if last_button: + button.order = last_button.order + 1 db.session.add(button) - db.session.flush() + db.session.commit() return button -@button.get("/", response_model=list[ButtonGet]) -def get_buttons(offset: int = 0, limit: int = 100): - return db.session.query(Button).offset(offset).limit(limit).all() +@button.get("/", response_model=CategoryGet) +def get_buttons(category_id: int): + category = db.session.query(Category).filter(Category.id == category_id).one_or_none() + if not category: + raise HTTPException(status_code=404, detail="Category does not exist") + return category @button.get("/{button_id}", response_model=ButtonGet) -def get_button(button_id: int): +def get_button(button_id: int, category_id: int): + category = db.session.query(Category).filter(Category.id == category_id).one_or_none() + if not category: + raise HTTPException(status_code=404, detail="Category does not exist") button = db.session.query(Button).filter(Button.id == button_id).one_or_none() if not button: raise HTTPException(status_code=404, detail="Button does not exist") + if button.category_id != category_id: + raise HTTPException(status_code=404, detail="Button is not this category") return button @button.delete("/{button_id}", response_model=None) -def remove_button(button_id: int): +def remove_button(button_id: int, category_id: int): + category = db.session.query(Category).filter(Category.id == category_id).one_or_none() + if not category: + raise HTTPException(status_code=404, detail="Category does not exist") button = db.session.query(Button).filter(Button.id == button_id).one_or_none() if not button: raise HTTPException(status_code=404, detail="Button does not exist") + if button.category_id != category_id: + raise HTTPException(status_code=404, detail="Button is not in this category") db.session.delete(button) - db.session.flush() + db.session.query(Button).filter(Button.order > button.order).update({"order": Button.order - 1}) + db.session.commit() -@button.patch("/{button_id}", response_model=ButtonGet) -def update_button(button_inp: ButtonUpdate, button_id: int): - button = db.session.query(Button).filter(Button.id == button_id) - if not button.one_or_none(): +@button.patch("/{button_id}", response_model=ButtonUpdate) +def update_button(button_inp: ButtonUpdate, button_id: int, category_id: int): + query = db.session.query(Button).filter(Button.id == button_id) + button = query.one_or_none() + last_button = ( + db.session.query(Button).filter(Button.category_id == category_id).order_by(Button.order.desc()).first() + ) + category = db.session.query(Category).filter(Category.id == category_id).one_or_none() + + if not category: + raise HTTPException(status_code=404, detail="Category does not exist") + if not button: raise HTTPException(status_code=404, detail="Button does not exist") if not any(button_inp.dict().values()): raise HTTPException(status_code=400, detail="Empty schema") - button.update( - button_inp.dict(exclude_unset=True) - ) - db.session.flush() - patched = button.one() - return patched + if button.category_id != category_id: + raise HTTPException(status_code=404, detail="Button is not this category") + + if button_inp.order: + if last_button and (button_inp.order > last_button.order + 1): + raise HTTPException( + status_code=400, + detail=f"Can`t create button with order {button_inp.order}. " f"Last category is {last_button.order}", + ) + if button_inp.order < 1: + raise HTTPException(status_code=400, detail="Order can`t be less than 1") + if button.order > button_inp.order: + db.session.query(Button).filter(Button.order < button.order).update({"order": Button.order + 1}) + elif button.order < button_inp.order: + db.session.query(Button).filter(Button.order > button.order).update({"order": Button.order - 1}) + + query.update(button_inp.dict(exclude_unset=True, exclude_none=True)) + db.session.commit() + return button diff --git a/services_backend/routes/category.py b/services_backend/routes/category.py index 6abec1a..524ea2f 100644 --- a/services_backend/routes/category.py +++ b/services_backend/routes/category.py @@ -9,48 +9,77 @@ @category.post("/", response_model=CategoryGet) def create_category(category_inp: CategoryCreate): - category = Category(**category_inp.dict()) + last_category = db.session.query(Category).order_by(Category.order.desc()).first() + category = Category(**category_inp.dict(exclude_none=True)) + if last_category: + category.order = last_category.order + 1 db.session.add(category) - db.session.flush() + db.session.commit() return category -@category.get("/", response_model=list[CategoryGet]) +@category.get("/", response_model=list[CategoryGet], response_model_exclude_none=True) def get_categories(offset: int = 0, limit: int = 100): - return db.session.query(Category).offset(offset).limit(limit).all() + if (offset < 0) or (limit < 0): + raise HTTPException(400, detail="Offset or limit cant be negative") + return [ + CategoryGet.from_orm(row).dict(exclude={"buttons"}) + for row in db.session.query(Category).order_by(Category.order).offset(offset).limit(limit).all() + ] -@category.get("/{category_id}", response_model=CategoryGet) +@category.get("/{category_id}", response_model=CategoryGet, response_model_exclude_none=True) def get_category(category_id: int): category = db.session.query(Category).filter(Category.id == category_id).one_or_none() if not category: raise HTTPException(status_code=404, detail="Category does not exist") - return category + return { + "id": category_id, + "order": category.order, + "name": category.name, + "type": category.type, + } @category.delete("/{category_id}", response_model=None) def remove_category(category_id: int): category = db.session.query(Category).filter(Category.id == category_id).one_or_none() - if category is None: + if not category: raise HTTPException(status_code=404, detail="Category does not exist") - delete = db.session.query(Category).filter(Category.id == category_id).one_or_none() for button in db.session.query(Button).filter(Button.category_id == category_id).all(): db.session.delete(button) db.session.flush() - db.session.delete(delete) - db.session.flush() + db.session.query(Category).filter(Category.order > category.order).update({"order": Category.order - 1}) + db.session.delete(category) + db.session.commit() @category.patch("/{category_id}", response_model=CategoryUpdate) def update_category(category_inp: CategoryUpdate, category_id: int): - category = db.session.query(Category).filter(Category.id == category_id) - if not category.one_or_none(): + category = db.session.query(Category).filter(Category.id == category_id).one_or_none() + last_category = db.session.query(Category).order_by(Category.order.desc()).first() + + if not category: raise HTTPException(status_code=404, detail="Category does not exist") if not any(category_inp.dict().values()): raise HTTPException(status_code=400, detail="Empty schema") - category.update( - category_inp.dict(exclude_unset=True) - ) - db.session.flush() - patched = category.one() - return patched + + if category_inp.order: + if category_inp.order < 1: + raise HTTPException(status_code=400, detail="Order can`t be less than 1") + if last_category and (category_inp.order > last_category.order): + raise HTTPException( + status_code=400, + detail=f"Can`t create category with order {category_inp.order}. " + f"Last category is {last_category.order}", + ) + + if category.order > category_inp.order: + db.session.query(Category).filter(Category.order < category.order).update({"order": Category.order + 1}) + elif category.order < category_inp.order: + db.session.query(Category).filter(Category.order > category.order).update({"order": Category.order - 1}) + + query = db.session.query(Category).filter(Category.id == category_id) + query.update(category_inp.dict(exclude_unset=True, exclude_none=True)) + db.session.commit() + return category diff --git a/services_backend/routes/models/button.py b/services_backend/routes/models/button.py index d608542..938436a 100644 --- a/services_backend/routes/models/button.py +++ b/services_backend/routes/models/button.py @@ -2,19 +2,25 @@ class ButtonCreate(Base): - category_id: int icon: str | None name: str | None + link: str | None + type: str | None class ButtonUpdate(Base): category_id: int | None icon: str | None name: str | None + order: int | None + link: str | None + type: str | None class ButtonGet(Base): id: int - category_id: int + order: int icon: str | None name: str | None + link: str | None + type: str | None diff --git a/services_backend/routes/models/category.py b/services_backend/routes/models/category.py index c5918f9..0c48093 100644 --- a/services_backend/routes/models/category.py +++ b/services_backend/routes/models/category.py @@ -8,12 +8,14 @@ class CategoryCreate(Base): class CategoryUpdate(Base): + order: int | None type: str | None name: str | None class CategoryGet(Base): id: int + order: int type: str | None name: str | None - buttons: list[ButtonGet] + buttons: list[ButtonGet] | None diff --git a/tests/api/button.py b/tests/api/button.py index 67cf30c..b0a7874 100644 --- a/tests/api/button.py +++ b/tests/api/button.py @@ -5,86 +5,149 @@ class TestButton: - _url = '/button/' settings = get_settings() - def test_get_success(self, client, db_button): - res = client.get(self._url) + def test_get_success(self, client, db_button, db_category): + res = client.get(f"/category/{db_category.id}/button/{db_button.id}") assert res.status_code == status.HTTP_200_OK - assert len(res.json()) == 1 - assert res.json()[0]['id'] == db_button.id + assert res.json()['id'] == db_button.id def test_post_success(self, client, db_category, dbsession): body = { - "category_id": db_category.id, "icon": "https://lh3.googleusercontent.com/yURn6ISxDySTdXZAW2PUcADMnU3y9YX0M1RyXOH8a3sa1Tr0pHhPLGw5BKuiLiXa3Eh0fyHm7Dfsd9FodK3fxJge6g=w640-h400-e365-rj-sc0x00ffffff", "name": "string", + "link": "google.com", + "type": "test", } - res = client.post(self._url, data=json.dumps(body)) + res = client.post(f"/category/{db_category.id}/button/", data=json.dumps(body)) assert res.status_code == status.HTTP_200_OK res_body = res.json() - assert res_body["category_id"] == body["category_id"] assert res_body["icon"] == body["icon"] + assert res_body["order"] == 1 assert res_body["name"] == body["name"] - db_button_created: Button = ( - dbsession.query(Button).filter(Button.category_id == body["category_id"]).one_or_none() - ) + assert res_body["link"] == body["link"] + assert res_body["type"] == body["type"] + db_button_created: Button = dbsession.query(Button).filter(Button.id == res_body["id"]).one_or_none() assert db_button_created assert db_button_created.icon == body["icon"] - assert db_button_created.category_id == body["category_id"] assert db_button_created.name == body["name"] assert db_button_created.category == db_category + assert db_button_created.link == body["link"] + assert db_button_created.type == body["type"] + assert db_button_created.order == 1 - def test_get_by_id_success(self, client, db_button): - res = client.get(f'{self._url}{db_button.id}') + def test_get_by_id_success(self, client, db_button, db_category): + res = client.get(f"/category/{db_category.id}/button/{db_button.id}") assert res.status_code == status.HTTP_200_OK res_body = res.json() - assert res_body['category_id'] == db_button.category_id assert res_body['icon'] == db_button.icon assert res_body['name'] == db_button.name + assert res_body['order'] == db_button.order + assert res_body['link'] == db_button.link + assert res_body['type'] == db_button.type - def test_delete_by_id_success(self, client, dbsession, db_button): - res = client.delete(f"{self._url}{db_button.id}") + def test_delete_by_id_success(self, client, dbsession, db_button, db_category): + res = client.delete(f"/category/{db_category.id}/button/{db_button.id}") assert res.status_code == status.HTTP_200_OK q = dbsession.query(Button).filter(Button.id == db_button.id) - assert q.one_or_none() is None - get_res = client.get(f"{self._url}{db_button.id}") + assert not q.one_or_none() + get_res = client.get(f"/category/{db_category.id}/button/{db_button.id}") assert get_res.status_code == status.HTTP_404_NOT_FOUND - def test_patch_by_id_success(self, db_button, dbsession, client): - body = {"category_id": db_button.category_id, "icon": "cool icon", "name": "nice name"} - res = client.patch(f"{self._url}{db_button.id}", data=json.dumps(body)) + def test_patch_by_id_success(self, db_button, client, db_category): + body = {"icon": "cool icon", "name": "nice name", "order": 2, "link": "ya.ru", "type": "nice type"} + res = client.patch(f"/category/{db_category.id}/button/{db_button.id}", data=json.dumps(body)) assert res.status_code == status.HTTP_200_OK res_body = res.json() - assert res_body["category_id"] == body["category_id"] assert res_body["icon"] == body["icon"] + assert res_body["order"] == body["order"] assert res_body["name"] == body["name"] + assert res_body["link"] == body["link"] + assert res_body["type"] == body["type"] - def test_patch_unset_params(self, client, db_button, dbsession): + def test_patch_unset_params(self, client, db_button, db_category): body = {} - res = client.patch(f"{self._url}{db_button.id}", data=json.dumps(body)) + res = client.patch(f"/category/{db_category.id}/button/{db_button.id}", data=json.dumps(body)) assert res.status_code == status.HTTP_400_BAD_REQUEST - body["category_id"] = db_button.category_id body["icon"] = "string" - res = client.patch(f"{self._url}{db_button.id}", data=json.dumps(body)) + body["order"] = 1 + res = client.patch(f"/category/{db_category.id}/button/{db_button.id}", data=json.dumps(body)) assert res.status_code == status.HTTP_200_OK assert res.json()["icon"] == body["icon"] - body_name = { - "name": "string" - } - res = client.patch(f"{self._url}{db_button.id}", data=json.dumps(body_name)) + body_name = {"name": "string", "order": 1} + res = client.patch(f"/category/{db_category.id}/button/{db_button.id}", data=json.dumps(body_name)) assert res.status_code == status.HTTP_200_OK assert res.json()["name"] == body_name["name"] - def test_get_by_id_not_found(self, client, db_button): - res = client.get(f'{self._url}{db_button.id + 1}') + def test_get_by_id_not_found(self, client, db_button, db_category): + res = client.get(f"/category/{db_category.id}/button/{db_button.id + 1}") assert res.status_code == status.HTTP_404_NOT_FOUND - def test_delete_by_id_not_found(self, client, db_button): - res = client.delete(f"{self._url}{db_button.id + 1}") + def test_delete_by_id_not_found(self, client, db_button, db_category): + res = client.delete(f"/category/{db_category.id}/button/{db_button.id + 1}") assert res.status_code == status.HTTP_404_NOT_FOUND - def test_patch_by_id_not_found(self, client, db_button): - body = {"category_id": db_button.category_id, "icon": "cool icon", "name": "nice name"} - res = client.patch(f"{self._url}{db_button.id + 1}", data=json.dumps(body)) + def test_patch_by_id_not_found(self, client, db_button, db_category): + body = {"icon": "cool icon", "name": "nice name"} + res = client.patch(f"/category/{db_category.id}/button/{db_button.id + 1}", data=json.dumps(body)) assert res.status_code == status.HTTP_404_NOT_FOUND + + def test_create_first(self, client, db_button, db_category): + body = { + "icon": "test", + "name": "test", + "link": "test", + "type": "test", + } + + res = client.post(f"/category/{db_category.id}/button/", data=json.dumps(body)) + assert res.status_code == status.HTTP_200_OK + + res = client.patch(f"/category/{db_category.id}/button/{res.json()['id']}", data=json.dumps({"order": 1})) + assert res.json()["order"] == 1 + res_old = client.get(f"/category/{db_category.id}/button/{db_button.id}") + assert res_old.json()["order"] == 2 + + def test_patch_order_fail(self, client, db_button, db_category): + body = { + "icon": "test", + "name": "new", + "link": "test", + "type": "test", + } + res1 = client.post(f"/category/{db_category.id}/button/", data=json.dumps(body)) + assert res1.status_code == status.HTTP_200_OK + + body_patch = { + "name": db_button.name, + "order": 44, + } + res = client.patch(f"/category/{db_category.id}/button/{res1.json()['id']}", data=json.dumps(body_patch)) + assert res.status_code == status.HTTP_400_BAD_REQUEST + + def test_patch_negative_order_fail(self, db_button, client, db_category): + body = { + "icon": "test", + "name": "new", + "link": "test", + "type": "test", + } + res = client.post(f"/category/{db_category.id}/button/", data=json.dumps(body)) + res1 = client.patch(f"/category/{db_category.id}/button/{res.json()['id']}", data=json.dumps({"order": -10})) + assert res1.status_code == status.HTTP_400_BAD_REQUEST + + def test_delete_order(self, db_button, client, db_category): + body = { + "icon": "test", + "name": "new", + "link": "test", + "type": "test", + } + res1 = client.post(f"/category/{db_category.id}/button/", data=json.dumps(body)) + assert res1.status_code == status.HTTP_200_OK + + res = client.delete(f"/category/{db_category.id}/button/{res1.json()['id']}") + assert res.status_code == status.HTTP_200_OK + + res = client.get(f"/category/{db_category.id}/button/{db_button.id}") + assert res.json()['order'] == 1 diff --git a/tests/api/category.py b/tests/api/category.py index cb9dde8..7531257 100644 --- a/tests/api/category.py +++ b/tests/api/category.py @@ -1,5 +1,4 @@ import json -import pytest from starlette import status from services_backend.settings import get_settings from services_backend.models.database import Category @@ -17,7 +16,7 @@ def test_get_success(self, client, db_category): assert res_body[0]['id'] == db_category.id assert res_body[0]['type'] == db_category.type assert res_body[0]['name'] == db_category.name - assert res_body[0]['buttons'] == [] + assert res_body[0]['order'] == db_category.order def test_post_success(self, client, dbsession): body = {"type": "string", "name": "string"} @@ -26,11 +25,13 @@ def test_post_success(self, client, dbsession): res_body = res.json() assert res_body["type"] == body["type"] assert res_body["name"] == body["name"] + assert res_body["order"] == 1 db_category_created: Category = dbsession.query(Category).filter(Category.name == body["name"]).one_or_none() assert db_category_created assert db_category_created.name == body["name"] assert db_category_created.type == body["type"] - assert db_category_created.buttons == [] + assert db_category_created.order == 1 + assert not db_category_created.buttons def test_get_by_id_success(self, client, db_category): res = client.get(f'{self._url}{db_category.id}') @@ -39,7 +40,7 @@ def test_get_by_id_success(self, client, db_category): assert res_body['id'] == db_category.id assert res_body['type'] == db_category.type assert res_body['name'] == db_category.name - assert res_body['buttons'] == [] + assert res_body['order'] == db_category.order def test_delete_by_id_success(self, client, dbsession, db_category): res = client.delete(f'{self._url}{db_category.id}') @@ -49,31 +50,30 @@ def test_delete_by_id_success(self, client, dbsession, db_category): get_res = client.get(f'{self._url}{db_category.id}') assert get_res.status_code == status.HTTP_404_NOT_FOUND - def test_patch_by_id_success(self, client, db_category, dbsession): - body = {"type": "string", "name": "string"} + def test_patch_by_id_success(self, client, db_category): + body = {"type": "test", "name": "test", "order": 1} res = client.patch(f"{self._url}{db_category.id}", data=json.dumps(body)) assert res.status_code == status.HTTP_200_OK res_body = res.json() assert res_body['type'] == body['type'] assert res_body['name'] == body['name'] + assert res_body['order'] == body['order'] - def test_patch_unset_params(self, client, db_category, dbsession): + def test_patch_unset_params(self, client, db_category): body = {} res = client.patch(f"{self._url}{db_category.id}", data=json.dumps(body)) assert res.status_code == status.HTTP_400_BAD_REQUEST - body["type"] = "string" + body["order"] = 1 res = client.patch(f"{self._url}{db_category.id}", data=json.dumps(body)) assert res.status_code == status.HTTP_200_OK - assert res.json()["type"] == body["type"] - body_name = { - "name": "string" - } - res = client.patch(f"{self._url}{db_category.id}", data=json.dumps(body_name)) + assert res.json()["order"] == body["order"] + body_ord = {"order": 1} + res = client.patch(f"{self._url}{db_category.id}", data=json.dumps(body_ord)) assert res.status_code == status.HTTP_200_OK - assert res.json()["name"] == body_name["name"] + assert res.json()["order"] == body_ord["order"] def test_get_by_id_not_found(self, client, db_category): - res = client.get(f'{self._url}{db_category.id + 1}') + res = client.get(f'{self._url}/{db_category.id + 1}') assert res.status_code == status.HTTP_404_NOT_FOUND def test_delete_by_id_not_found(self, client, db_category): @@ -81,6 +81,74 @@ def test_delete_by_id_not_found(self, client, db_category): assert res.status_code == status.HTTP_404_NOT_FOUND def test_patch_by_id_not_found(self, client, db_category): - body = {"type": "string", "name": "string"} + body = {"type": "string", "name": "string", "order": 1} res = client.patch(f"{self._url}{db_category.id + 1}", data=json.dumps(body)) assert res.status_code == status.HTTP_404_NOT_FOUND + + def test_create_first(self, client, db_category): + body = { + "name": "test", + "type": "test", + } + + res = client.post(self._url, data=json.dumps(body)) + assert res.status_code == status.HTTP_200_OK + res1 = client.patch(f"{self._url}{res.json()['id']}", data=json.dumps({"order": 1})) + assert res1.status_code == status.HTTP_200_OK + assert res1.json()["order"] == 1 + + res_old = client.get(f"{self._url}{db_category.id}") + assert res_old.json()["order"] == 2 + client.delete(f"{self._url}{res.json()['id']}") + + def test_patch_order(self, client, db_category): + body = { + "name": "new", + "type": "test", + } + res1 = client.post(self._url, data=json.dumps(body)) + assert res1.status_code == status.HTTP_200_OK + res = client.patch(f"{self._url}{res1.json()['id']}", data=json.dumps({"order": 1})) + assert res.status_code == status.HTTP_200_OK + assert res.json()["order"] == 1 + + res = client.get(f"{self._url}{db_category.id}") + assert res.json()["order"] == 2 + client.delete(f"{self._url}{res1.json()['id']}") + + def test_create_third_fail(self, db_category, client): + body = { + "name": "new", + "type": "test", + } + res1 = client.post(self._url, data=json.dumps(body)) + assert res1.status_code == status.HTTP_200_OK + res = client.patch(f"{self._url}{res1.json()['id']}", data=json.dumps({"order": 33})) + assert res.status_code == status.HTTP_400_BAD_REQUEST + client.delete(f"{self._url}{res1.json()['id']}") + + def test_create_negative_order_fail(self, db_category, client): + body = { + "name": "new", + "type": "test", + } + res1 = client.post(self._url, data=json.dumps(body)) + assert res1.status_code == status.HTTP_200_OK + res = client.patch(f"{self._url}{res1.json()['id']}", data=json.dumps({"order": -1})) + assert res.status_code == status.HTTP_400_BAD_REQUEST + client.delete(f"{self._url}{res1.json()['id']}") + + def test_delete_order(self, db_category, client): + body = { + "name": "new", + "type": "test", + } + res1 = client.post(self._url, data=json.dumps(body)) + assert res1.status_code == status.HTTP_200_OK + assert res1.json()['order'] == 2 + + res = client.delete(f"{self._url}{db_category.id}") + assert res.status_code == status.HTTP_200_OK + + res = client.get(f"{self._url}{res1.json()['id']}") + assert res.json()['order'] == 1 diff --git a/tests/api/conftest.py b/tests/api/conftest.py index 9cb2bdc..203b3c8 100644 --- a/tests/api/conftest.py +++ b/tests/api/conftest.py @@ -4,23 +4,25 @@ @pytest.fixture def db_category(dbsession): - _category = Category(id=666, name='categoty', type='some-type') - dbsession.add(_category) - dbsession.flush() - yield _category - query = dbsession.query(Category).filter(Category.id == _category.id) - if query.one_or_none(): - dbsession.delete(query.one()) - dbsession.flush() + category = Category(name='categoty', type='some-type') + dbsession.add(category) + dbsession.commit() + category = dbsession.query(Category).filter(Category.id == category.id).one_or_none() + yield category + for button in dbsession.query(Button).filter(Button.category_id == category.id).all(): + dbsession.delete(button) + dbsession.commit() + dbsession.delete(category) + dbsession.commit() @pytest.fixture def db_button(dbsession, db_category): - _button = Button(id=42, name='button', category_id=db_category.id) + _button = Button(name='button', category_id=db_category.id, icon='test', link='g', type='d') dbsession.add(_button) - dbsession.flush() + dbsession.commit() + _button = dbsession.query(Button).filter(Button.id == _button.id).one_or_none() yield _button - query = dbsession.query(Button).filter(Button.id == _button.id) - if query.one_or_none(): - dbsession.delete(query.one()) - dbsession.flush() + if _button: + dbsession.delete(_button) + dbsession.commit() diff --git a/tests/conftest.py b/tests/conftest.py index ebd529f..d967a94 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,8 +16,8 @@ def client(): @pytest.fixture(scope='session') def dbsession() -> Session: settings = get_settings() - engine = create_engine(settings.DB_DSN) - TestingSessionLocal = sessionmaker(autocommit=True, autoflush=False, bind=engine) + engine = create_engine(settings.DB_DSN, execution_options={"isolation_level": "AUTOCOMMIT"}) + TestingSessionLocal = sessionmaker(bind=engine) + Base.metadata.drop_all(bind=engine) Base.metadata.create_all(bind=engine) yield TestingSessionLocal() - Base.metadata.drop_all(bind=engine)