Skip to content

Commit fda0ed7

Browse files
[DOP-19793] Make queue name unique within a group (#119)
1 parent cd6386a commit fda0ed7

File tree

12 files changed

+89
-12
lines changed

12 files changed

+89
-12
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Now `Queue.name` is made unique within `group_id`

syncmaster/db/migrations/versions/2023-11-23_0003_create_queue_table.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@
2121
def upgrade():
2222
op.create_table(
2323
"queue",
24-
sa.Column("name", sa.String(length=128), nullable=False),
2524
sa.Column("id", sa.BigInteger(), nullable=False),
25+
sa.Column("name", sa.String(length=128), nullable=False),
26+
sa.Column("slug", sa.String(length=256), nullable=False),
2627
sa.Column("group_id", sa.BigInteger(), nullable=False),
2728
sa.Column("description", sa.String(length=512), nullable=False),
2829
sa.Column("created_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False),
@@ -41,7 +42,7 @@ def upgrade():
4142
ondelete="CASCADE",
4243
),
4344
sa.PrimaryKeyConstraint("id", name=op.f("pk__queue")),
44-
sa.UniqueConstraint("name", name=op.f("uq__queue__name")),
45+
sa.UniqueConstraint("slug", name=op.f("uq__queue__slug")),
4546
)
4647
op.create_index(op.f("ix__queue__group_id"), "queue", ["group_id"], unique=False)
4748

syncmaster/db/models/group.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class Group(Base, TimestampMixin, DeletableMixin):
7777

7878
owner: Mapped[User] = relationship(User)
7979
members: Mapped[list[User]] = relationship(User, secondary="user_group")
80-
queue: Mapped[Queue] = relationship(back_populates="group")
80+
queue: Mapped[Queue] = relationship(back_populates="group", cascade="all, delete-orphan")
8181

8282
search_vector: Mapped[str] = mapped_column(
8383
TSVECTOR,

syncmaster/db/models/queue.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717

1818

1919
class Queue(Base, ResourceMixin, TimestampMixin, DeletableMixin):
20-
name: Mapped[str] = mapped_column(String(128), nullable=False, unique=True)
20+
name: Mapped[str] = mapped_column(String(128), nullable=False)
21+
slug: Mapped[str] = mapped_column(String(256), nullable=False, unique=True)
2122

2223
transfers: Mapped[list[Transfer]] = relationship(back_populates="queue")
2324
group: Mapped[Group] = relationship(back_populates="queue")
@@ -31,4 +32,4 @@ class Queue(Base, ResourceMixin, TimestampMixin, DeletableMixin):
3132
)
3233

3334
def __repr__(self):
34-
return f"<Queue name={self.name} description={self.description}>"
35+
return f"<Queue name={self.name} slug={self.slug} description={self.description}>"

syncmaster/db/repositories/queue.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ async def get_resource_permission(self, user: User, resource_id: int) -> Permiss
197197
@staticmethod
198198
def _raise_error(err: DBAPIError) -> NoReturn:
199199
constraint = err.__cause__.__cause__.constraint_name
200-
if constraint == "uq__queue__name":
200+
if constraint == "uq__queue__slug":
201201
raise DuplicatedQueueNameError
202202

203203
raise SyncmasterError from err

syncmaster/schemas/v1/queue.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
# SPDX-FileCopyrightText: 2023-2024 MTS PJSC
22
# SPDX-License-Identifier: Apache-2.0
3-
from pydantic import BaseModel, Field, constr
3+
from pydantic import BaseModel, Field, computed_field, constr
44

55
from syncmaster.schemas.v1.page import PageSchema
66

77

88
class CreateQueueSchema(BaseModel):
99
name: constr(max_length=128, pattern=r"^[-_a-zA-Z0-9]+$") = Field( # noqa: F722
1010
...,
11-
description="Queue name",
11+
description="Queue name that allows letters, numbers, dashes, and underscores",
1212
)
1313
group_id: int = Field(..., description="Queue owner group id")
1414
description: str = Field(default="", description="Additional description")
1515

16+
@computed_field
17+
@property
18+
def slug(self) -> str:
19+
return f"{self.group_id}-{self.name}"
20+
1621

1722
class ReadQueueSchema(BaseModel):
1823
name: str
24+
slug: str
1925
description: str | None = None
2026
group_id: int
2127
id: int

tests/test_unit/conftest.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import secrets
2+
from collections.abc import AsyncGenerator
23

34
import pytest_asyncio
45
from sqlalchemy.ext.asyncio import AsyncSession
@@ -169,7 +170,7 @@ async def user_with_many_roles(session: AsyncSession, settings: Settings, simple
169170

170171

171172
@pytest_asyncio.fixture
172-
async def empty_group(session: AsyncSession, settings) -> MockGroup:
173+
async def empty_group(session: AsyncSession, settings) -> AsyncGenerator[MockGroup, None]:
173174
owner = await create_user(
174175
session=session,
175176
username="empty_group_owner",
@@ -195,7 +196,7 @@ async def empty_group(session: AsyncSession, settings) -> MockGroup:
195196

196197

197198
@pytest_asyncio.fixture
198-
async def group(session: AsyncSession, settings: Settings) -> MockGroup:
199+
async def group(session: AsyncSession, settings: Settings) -> AsyncGenerator[MockGroup, None]:
199200
owner = await create_user(
200201
session=session,
201202
username="notempty_group_owner",
@@ -239,7 +240,7 @@ async def group(session: AsyncSession, settings: Settings) -> MockGroup:
239240
async def mock_group(
240241
session: AsyncSession,
241242
settings: Settings,
242-
):
243+
) -> AsyncGenerator[MockGroup, None]:
243244
group_owner = await create_user(
244245
session=session,
245246
username=f"{secrets.token_hex(5)}_group_connection_owner",
@@ -289,7 +290,7 @@ async def group_queue(
289290
session: AsyncSession,
290291
settings: Settings,
291292
mock_group: MockGroup,
292-
) -> Queue:
293+
) -> AsyncGenerator[Queue, None]:
293294
queue = await create_queue(
294295
session=session,
295296
name=f"{secrets.token_hex(5)}_test_queue",

tests/test_unit/test_queue/test_create_queue.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ async def test_maintainer_plus_can_create_queue(
3535
"name": "New_queue",
3636
"description": "Some interesting description",
3737
"group_id": mock_group.group.id,
38+
"slug": f"{mock_group.group.id}-New_queue",
3839
}
3940
assert result.status_code == 200
4041
queue = (await session.scalars(select(Queue).filter_by(id=result.json()["id"]))).one()
@@ -43,6 +44,7 @@ async def test_maintainer_plus_can_create_queue(
4344
assert queue.group_id == mock_group.group.id
4445
assert queue.name == "New_queue"
4546
assert queue.description == "Some interesting description"
47+
assert queue.slug == f"{mock_group.group.id}-New_queue"
4648

4749
await session.delete(queue)
4850
await session.commit()
@@ -71,6 +73,7 @@ async def test_superuser_can_create_queue(
7173
"name": "New_queue",
7274
"description": "Some interesting description",
7375
"group_id": mock_group.group.id,
76+
"slug": f"{mock_group.group.id}-New_queue",
7477
}
7578
assert result.status_code == 200
7679
queue = (await session.scalars(select(Queue).filter_by(id=result.json()["id"]))).one()
@@ -79,6 +82,7 @@ async def test_superuser_can_create_queue(
7982
assert queue.group_id == mock_group.group.id
8083
assert queue.name == "New_queue"
8184
assert queue.description == "Some interesting description"
85+
assert queue.slug == f"{mock_group.group.id}-New_queue"
8286

8387
await session.delete(queue)
8488
await session.commit()
@@ -395,3 +399,56 @@ async def test_maintainer_plus_can_not_create_queue_with_duplicate_name_error(
395399
"details": None,
396400
},
397401
}
402+
403+
404+
async def test_maintainer_plus_can_create_queues_with_the_same_name_but_diff_groups(
405+
client: AsyncClient,
406+
session: AsyncSession,
407+
role_maintainer_plus: UserTestRoles,
408+
mock_group: MockGroup,
409+
group: MockGroup,
410+
):
411+
# Arrange
412+
mock_group_user = mock_group.get_member_of_role(role_maintainer_plus)
413+
group_user = group.get_member_of_role(role_maintainer_plus)
414+
415+
# Act
416+
queue_1 = await client.post(
417+
"v1/queues",
418+
headers={"Authorization": f"Bearer {mock_group_user.token}"},
419+
json={
420+
"name": "New_queue",
421+
"description": "Some interesting description",
422+
"group_id": mock_group.group.id,
423+
},
424+
)
425+
queue_2 = await client.post(
426+
"v1/queues",
427+
headers={"Authorization": f"Bearer {group_user.token}"},
428+
json={
429+
"name": "New_queue",
430+
"description": "Some interesting description",
431+
"group_id": group.group.id,
432+
},
433+
)
434+
queue_1_json = queue_1.json()
435+
queue_2_json = queue_2.json()
436+
437+
# Assert
438+
assert queue_1.status_code == 200
439+
assert queue_1_json == {
440+
"id": queue_1_json["id"],
441+
"name": "New_queue",
442+
"description": "Some interesting description",
443+
"group_id": mock_group.group.id,
444+
"slug": f"{mock_group.group.id}-New_queue",
445+
}
446+
assert queue_2.status_code == 200
447+
assert queue_2_json == {
448+
"id": queue_2_json["id"],
449+
"name": "New_queue",
450+
"description": "Some interesting description",
451+
"group_id": group.group.id,
452+
"slug": f"{group.group.id}-New_queue",
453+
}
454+
assert queue_1_json["slug"] != queue_2_json["slug"]

tests/test_unit/test_queue/test_read_queue.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ async def test_group_member_can_read_queue(
2828
"name": group_queue.name,
2929
"description": group_queue.description,
3030
"group_id": group_queue.group_id,
31+
"slug": f"{group_queue.group.id}-{group_queue.name}",
3132
}
3233
assert result.status_code == 200
3334

@@ -49,6 +50,7 @@ async def test_superuser_can_read_queue(
4950
"name": group_queue.name,
5051
"description": group_queue.description,
5152
"group_id": group_queue.group_id,
53+
"slug": f"{group_queue.group.id}-{group_queue.name}",
5254
}
5355
assert result.status_code == 200
5456

tests/test_unit/test_queue/test_read_queues.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ async def test_group_member_can_read_queues(
3535
"name": group_queue.name,
3636
"description": group_queue.description,
3737
"group_id": mock_group.id,
38+
"slug": f"{mock_group.id}-{group_queue.name}",
3839
},
3940
],
4041
"meta": {
@@ -73,6 +74,7 @@ async def test_superuser_can_read_queues(
7374
"name": group_queue.name,
7475
"description": group_queue.description,
7576
"group_id": mock_group.id,
77+
"slug": f"{mock_group.id}-{group_queue.name}",
7678
},
7779
],
7880
"meta": {
@@ -216,6 +218,7 @@ async def test_search_queues_with_query(
216218
"name": group_queue.name,
217219
"description": group_queue.description,
218220
"group_id": mock_group.id,
221+
"slug": f"{mock_group.id}-{group_queue.name}",
219222
},
220223
],
221224
}

0 commit comments

Comments
 (0)