diff --git a/migrations/versions/52230bfc3f86_56_add_is_hidden_field.py b/migrations/versions/52230bfc3f86_56_add_is_hidden_field.py new file mode 100644 index 0000000..6d00b8f --- /dev/null +++ b/migrations/versions/52230bfc3f86_56_add_is_hidden_field.py @@ -0,0 +1,29 @@ +"""#56_add_is_hidden_field + +Revision ID: 52230bfc3f86 +Revises: 1bb798899506 +Create Date: 2024-07-29 20:05:34.777852 + +""" + +import sqlalchemy as sa +from alembic import op + + +# revision identifiers, used by Alembic. +revision = '52230bfc3f86' +down_revision = '1bb798899506' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('button', sa.Column('is_hidden', sa.Boolean(), nullable=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('button', 'is_hidden') + # ### end Alembic commands ### diff --git a/services_backend/models/database.py b/services_backend/models/database.py index f79fd0b..611beac 100644 --- a/services_backend/models/database.py +++ b/services_backend/models/database.py @@ -63,6 +63,7 @@ class Button(Base): icon: Mapped[str] = mapped_column(String) link: Mapped[str] = mapped_column(String) type: Mapped[Type] = mapped_column(DbEnum(Type, native_enum=False), nullable=False) + is_hidden: Mapped[bool] = mapped_column(Boolean, default=False) _scopes: Mapped[list[Scope]] = relationship("Scope", back_populates="button", lazy='joined', cascade='delete') diff --git a/services_backend/routes/button.py b/services_backend/routes/button.py index 3468b28..94cfd28 100644 --- a/services_backend/routes/button.py +++ b/services_backend/routes/button.py @@ -22,8 +22,9 @@ class ButtonCreate(Base): name: str = Field(description='Название кнопки') link: str = Field(description='Ссылка, на которую перенаправляет кнопка') type: Type = Field(description='Тип открываемой ссылки (Ссылка приложения/Браузер в приложении/Браузер') - required_scopes: set[str] | None = Field(description='Каким скоупы нужны, чтобы кнопка была доступна', default=None) - optional_scopes: set[str] | None = Field(description='Каким скоупы желательны', default=None) + is_hidden: bool = Field(description='Спрятана ли кнопка от пользователя', default=False) + required_scopes: set[str] | None = Field(description='Какие скоупы нужны, чтобы кнопка была доступна', default=None) + optional_scopes: set[str] | None = Field(description='Какиеп скоупы желательны', default=None) class ButtonUpdate(Base): @@ -35,13 +36,15 @@ class ButtonUpdate(Base): type: Type | None = Field( description='Тип открываемой ссылки (Ссылка приложения/Браузер в приложении/Браузер', default=None ) - required_scopes: set[str] | None = Field(description='Каким скоупы нужны, чтобы кнопка была доступна', default=None) - optional_scopes: set[str] | None = Field(description='Каким скоупы желательны', default=None) + is_hidden: bool | None = Field(description='Спрятана ли кнопка от пользователя', default=None) + required_scopes: set[str] | None = Field(description='Какие скоупы нужны, чтобы кнопка была доступна', default=None) + optional_scopes: set[str] | None = Field(description='Какие скоупы желательны', default=None) class ButtonView(Enum): ACTIVE = "active" BLOCKED = "blocked" + HIDDEN = "hidden" class ButtonGet(Base): @@ -120,7 +123,9 @@ def get_buttons( for button in category.buttons: view = ButtonView.ACTIVE scopes = set() - if button.required_scopes - user_scopes: + if button.is_hidden: + view = ButtonView.HIDDEN + elif button.required_scopes - user_scopes: view = ButtonView.BLOCKED else: scopes |= button.required_scopes @@ -169,7 +174,9 @@ def get_button( raise HTTPException(status_code=404, detail="Button is not this category") view = ButtonView.ACTIVE scopes = set() - if button.required_scopes - user_scopes: + if button.is_hidden: + view = ButtonView.HIDDEN + elif button.required_scopes - user_scopes: view = ButtonView.BLOCKED else: scopes |= button.required_scopes @@ -297,7 +304,9 @@ def get_service( raise HTTPException(status_code=404, detail="Button does not exist") view = ButtonView.ACTIVE scopes = set() - if button.required_scopes - user_scopes: + if button.is_hidden: + view = ButtonView.HIDDEN + elif button.required_scopes - user_scopes: view = ButtonView.BLOCKED else: scopes |= button.required_scopes diff --git a/services_backend/routes/category.py b/services_backend/routes/category.py index fb1cc84..5fa0fdf 100644 --- a/services_backend/routes/category.py +++ b/services_backend/routes/category.py @@ -6,7 +6,7 @@ from fastapi_sqlalchemy import db from pydantic import Field, conint -from services_backend.models.database import Button, Category, Type +from services_backend.models.database import Button, Category from services_backend.schemas import Base from .button import ButtonGet, ButtonView @@ -109,7 +109,9 @@ def get_categories( for button in row.buttons: view = ButtonView.ACTIVE scopes = set() - if button.required_scopes - user_scopes: + if button.is_hidden: + view = ButtonView.HIDDEN + elif button.required_scopes - user_scopes: view = ButtonView.BLOCKED else: scopes |= button.required_scopes @@ -119,7 +121,7 @@ def get_categories( "id": button.id, "icon": button.icon, "name": button.name, - "link": (button.link if view == ButtonView.ACTIVE else None), + "link": button.link if view == ButtonView.ACTIVE else None, "order": button.order, "type": button.type, "view": view.value, diff --git a/tests/api/button.py b/tests/api/button.py index 821c90e..a789d3f 100644 --- a/tests/api/button.py +++ b/tests/api/button.py @@ -235,3 +235,66 @@ def test_type_not_enum(client, dbsession, db_category): } res = client.post(f"/category/{db_category.id}/button", data=json.dumps(body)) assert res.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + + +def test_post_hidden_success(client, dbsession, db_category): + body = {"icon": "qq", "name": "new", "link": "google.com", "type": "inapp", "is_hidden": True} + 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["icon"] == body["icon"] + assert res_body["order"] == 1 + assert res_body["name"] == body["name"] + 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.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 + assert db_button_created.is_hidden == True + dbsession.delete(db_button_created) + dbsession.commit() + + +def test_get_hidden_by_id_success(client, dbsession, db_button, db_category): + db_button.is_hidden = True + dbsession.commit() + 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['icon'] == db_button.icon + assert res_body['name'] == db_button.name + assert res_body['order'] == db_button.order + assert res_body['link'] is None + assert res_body['type'] == db_button.type + assert res_body['view'] == "hidden" + + +def test_patch_to_hide_success(client, db_button, db_category): + body = {"is_hidden": True} + 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["icon"] == db_button.icon + assert res_body["order"] == db_button.order + assert res_body["name"] == db_button.name + assert res_body["link"] == db_button.link + assert res_body["type"] == db_button.type + assert res_body["is_hidden"] == True + res = client.get(f"/category/{db_category.id}/button/{db_button.id}") + assert res.status_code == status.HTTP_200_OK + assert res.json()["link"] is None + assert res.json()["view"] == "hidden" + + +def test_delete_hidden_success(client, dbsession, db_button, db_category): + db_button.is_hidden = True + dbsession.commit() + 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 not q.one_or_none() diff --git a/tests/api/category.py b/tests/api/category.py index 255571d..faa104e 100644 --- a/tests/api/category.py +++ b/tests/api/category.py @@ -187,9 +187,10 @@ def test_delete_order(db_category, client): res = client.get(f"/category/{res1.json()['id']}") assert res.json()['order'] == 1 + client.delete(f"/category/{res1.json()['id']}") -def test_scopes(client, mocker: MockerFixture): +def test_scopes(client, dbsession, mocker: MockerFixture): user_mock = mocker.patch('auth_lib.fastapi.UnionAuth.__call__') user_mock.return_value = { "session_scopes": [{"id": 0, "name": "string", "comment": "string"}], @@ -238,3 +239,20 @@ def test_scopes(client, mocker: MockerFixture): res8 = client.get(f'/category/{id_}') assert res8.status_code == status.HTTP_404_NOT_FOUND + + category = dbsession.query(Category).filter(Category.id == id_).one_or_none() + dbsession.delete(category) + dbsession.commit() + + +def test_get_hidden_button_success(client, dbsession, db_button): + db_button.is_hidden = True + dbsession.commit() + res = client.get('/category', params={"info": "buttons"}) + assert res.status_code == status.HTTP_200_OK + res_body = res.json() + assert len(res_body) == 1 + assert len(res_body[0]["buttons"]) == 1 + assert res_body[0]["buttons"][0]["id"] == db_button.id + assert res_body[0]["buttons"][0]["view"] == "hidden" + assert "link" not in res_body[0]["buttons"][0]