Skip to content

Add search functionality to items endpoint with tests #1715

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 17 additions & 12 deletions backend/app/api/routes/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Any

from fastapi import APIRouter, HTTPException
from sqlmodel import func, select
from sqlmodel import func, select, or_

from app.api.deps import CurrentUser, SessionDep
from app.models import Item, ItemCreate, ItemPublic, ItemsPublic, ItemUpdate, Message
Expand All @@ -12,31 +12,36 @@

@router.get("/", response_model=ItemsPublic)
def read_items(
session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100
session: SessionDep,
current_user: CurrentUser,
skip: int = 0,
limit: int = 100,
search: str | None = None
) -> Any:
"""
Retrieve items.
"""

if current_user.is_superuser:
query = select(Item)
count_statement = select(func.count()).select_from(Item)
count = session.exec(count_statement).one()
statement = select(Item).offset(skip).limit(limit)
items = session.exec(statement).all()
else:
query = select(Item).where(Item.owner_id == current_user.id)
count_statement = (
select(func.count())
.select_from(Item)
.where(Item.owner_id == current_user.id)
)
count = session.exec(count_statement).one()
statement = (
select(Item)
.where(Item.owner_id == current_user.id)
.offset(skip)
.limit(limit)

if search:
search_filter = or_(
Item.title.contains(search, autoescape=True), # type: ignore
Item.description.contains(search, autoescape=True), # type: ignore
)
items = session.exec(statement).all()
query = query.where(search_filter)
count_statement = count_statement.where(search_filter)
count = session.exec(count_statement).one()
items = session.exec(query.offset(skip).limit(limit)).all()

return ItemsPublic(data=items, count=count)

Expand Down
99 changes: 99 additions & 0 deletions backend/app/tests/api/routes/test_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,102 @@ def test_delete_item_not_enough_permissions(
assert response.status_code == 400
content = response.json()
assert content["detail"] == "Not enough permissions"
def test_search_items_by_title(
client: TestClient, superuser_token_headers: dict[str, str], db: Session
) -> None:
# Create test items using the existing function
item1 = create_random_item(db)
item2 = create_random_item(db)

# Update the first item to have a specific title for testing
item1.title = "Alpha Item"
item1.description = "First item"
db.add(item1)
db.commit()

# Test search by title
response = client.get(
f"{settings.API_V1_STR}/items/?search=Alpha",
headers=superuser_token_headers,
)
assert response.status_code == 200
content = response.json()
assert content["count"] == 1
assert content["data"][0]["title"] == "Alpha Item"


def test_search_items_by_description(
client: TestClient, superuser_token_headers: dict[str, str], db: Session
) -> None:
# Create test items
item1 = create_random_item(db)
item2 = create_random_item(db)

# Update the first item to have a specific description for testing
item1.title = "First"
item1.description = "UniqueAlphaDescription" # More unique
db.add(item1)
db.commit()

# Test search by description
response = client.get(
f"{settings.API_V1_STR}/items/?search=UniqueAlpha", # More specific search
headers=superuser_token_headers,
)
assert response.status_code == 200
content = response.json()
assert content["count"] == 1
assert content["data"][0]["description"] == "UniqueAlphaDescription"


def test_search_items_no_results(
client: TestClient, superuser_token_headers: dict[str, str], db: Session
) -> None:
# Create test items
item = create_random_item(db)

# Update item to have specific content
item.title = "Alpha"
item.description = "First item"
db.add(item)
db.commit()

# Test search with no matches
response = client.get(
f"{settings.API_V1_STR}/items/?search=NotFound",
headers=superuser_token_headers,
)
assert response.status_code == 200
content = response.json()
assert content["count"] == 0
assert content["data"] == []


def test_search_items_with_pagination(
client: TestClient, superuser_token_headers: dict[str, str], db: Session
) -> None:
# Create multiple test items
item1 = create_random_item(db)
item2 = create_random_item(db)
item3 = create_random_item(db)

# Update items to have specific content for testing
item1.title = "Test Item 1"
item1.description = "First test"
item2.title = "Test Item 2"
item2.description = "Second test"
item3.title = "Other Item"
item3.description = "Not matching"

db.add_all([item1, item2, item3])
db.commit()

# Test search with pagination
response = client.get(
f"{settings.API_V1_STR}/items/?search=Test&skip=0&limit=2",
headers=superuser_token_headers,
)
assert response.status_code == 200
content = response.json()
assert content["count"] == 2
assert len(content["data"]) == 2
Empty file added openapi.json
Empty file.
2 changes: 1 addition & 1 deletion release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@
* 👷 Do not sync labels as it overrides manually added labels. PR [#1307](https://github.com/fastapi/full-stack-fastapi-template/pull/1307) by [@tiangolo](https://github.com/tiangolo).
* 👷 Use uv cache on GitHub Actions. PR [#1366](https://github.com/fastapi/full-stack-fastapi-template/pull/1366) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update GitHub Actions format. PR [#1363](https://github.com/fastapi/full-stack-fastapi-template/pull/1363) by [@tiangolo](https://github.com/tiangolo).
* 👷 Use `uv` for Python env to generate client. PR [#1362](https://github.com/fastapi/full-stack-fastapi-template/pull/1362) by [@tiangolo](https://github.com/tiangolo).
* 👷 Use `uv` for Python env to gener ate client. PR [#1362](https://github.com/fastapi/full-stack-fastapi-template/pull/1362) by [@tiangolo](https://github.com/tiangolo).
* 👷 Run tests from Python environment (with `uv`), not from Docker container. PR [#1361](https://github.com/fastapi/full-stack-fastapi-template/pull/1361) by [@tiangolo](https://github.com/tiangolo).
* 🔨 Update `generate-client.sh` script, make it fail on errors, fix generation. PR [#1360](https://github.com/fastapi/full-stack-fastapi-template/pull/1360) by [@tiangolo](https://github.com/tiangolo).
* 👷 Add GitHub Actions workflow to lint backend apart from tests. PR [#1358](https://github.com/fastapi/full-stack-fastapi-template/pull/1358) by [@tiangolo](https://github.com/tiangolo).
Expand Down
Loading