Skip to content

Commit f3043c7

Browse files
committed
feat: Blog Read API Endpoint [Issue #4]
1 parent 9cefd9e commit f3043c7

File tree

3 files changed

+113
-6
lines changed

3 files changed

+113
-6
lines changed

api/v1/routes/blog.py

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
from api.db.database import get_db
99
from api.v1.models.blog import Blog
1010
from api.v1.schemas.blog import (
11-
BlogCreateResponseSchema,
1211
BlogCreateSchema,
1312
BlogListItemResponseSchema,
1413
BlogListResponseSchema,
14+
BlogResponseSchema,
1515
)
1616

1717
blog = APIRouter(prefix="/blogs", tags=["Blog"])
@@ -21,13 +21,13 @@
2121

2222
@blog.post(
2323
"",
24-
response_model=BlogCreateResponseSchema,
24+
response_model=BlogResponseSchema,
2525
status_code=status.HTTP_201_CREATED,
2626
)
2727
async def create_blog(
2828
blog: BlogCreateSchema,
2929
db: Session = Depends(get_db),
30-
) -> BlogCreateResponseSchema:
30+
) -> BlogResponseSchema:
3131
try:
3232
existing_blog = db.query(Blog).filter(Blog.title == blog.title).first()
3333
if existing_blog:
@@ -48,7 +48,7 @@ async def create_blog(
4848
db.refresh(new_blog)
4949
logger.info(f"Blog post '{new_blog.title}' created successfully.")
5050

51-
return BlogCreateResponseSchema.model_validate(new_blog.__dict__)
51+
return BlogResponseSchema.model_validate(new_blog.__dict__)
5252

5353
except HTTPException as http_err:
5454
logger.warning(f"HTTP error occurred: {http_err.detail}")
@@ -127,3 +127,51 @@ async def list_blog(
127127
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
128128
detail="Internal server error.",
129129
)
130+
131+
132+
@blog.get(
133+
"/{id}",
134+
response_model=BlogResponseSchema,
135+
status_code=status.HTTP_200_OK,
136+
)
137+
async def read_blog(
138+
id: int,
139+
db: Session = Depends(get_db),
140+
) -> BlogResponseSchema:
141+
try:
142+
blog = (
143+
db.query(Blog)
144+
.filter(
145+
Blog.id == id,
146+
not_(Blog.is_deleted),
147+
)
148+
.first()
149+
)
150+
if not blog:
151+
logger.warning(f"Blog post with ID '{id}' not found.")
152+
raise HTTPException(
153+
status_code=status.HTTP_404_NOT_FOUND,
154+
detail="Blog post not found.",
155+
)
156+
157+
logger.info(f"Blog post with ID '{id}' retrieved successfully.")
158+
return BlogResponseSchema.model_validate(blog.__dict__)
159+
160+
except SQLAlchemyError as e:
161+
logger.error(f"Database error occurred: {e}")
162+
raise HTTPException(
163+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
164+
detail="Database error occurred.",
165+
)
166+
except ValueError as e:
167+
logger.error(f"Invalid blog post ID '{id}': {e}")
168+
raise HTTPException(
169+
status_code=status.HTTP_400_BAD_REQUEST,
170+
detail="Invalid blog post ID.",
171+
)
172+
except Exception as e:
173+
logger.error(f"Unexpected error occurred: {e}")
174+
raise HTTPException(
175+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
176+
detail="Unexpected error occurred.",
177+
)

api/v1/schemas/blog.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
from pydantic import BaseModel, Field
77

88

9-
class BlogCreateResponseSchema(BaseModel):
10-
"""Schema for representing a created blog post in the API response."""
9+
class BlogResponseSchema(BaseModel):
10+
"""Schema for representing a blog post in the API response."""
1111

1212
id: int
1313
title: str

tests/v1/blog/test_read_blog.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from datetime import datetime
2+
from unittest.mock import MagicMock
3+
4+
import pytest
5+
from fastapi.testclient import TestClient
6+
7+
from api.db.database import get_db
8+
from api.v1.models.blog import Blog
9+
from main import app
10+
11+
client = TestClient(app)
12+
13+
14+
@pytest.fixture
15+
def db_session_mock():
16+
return MagicMock()
17+
18+
19+
@pytest.fixture(autouse=True)
20+
def override_get_db(db_session_mock):
21+
app.dependency_overrides[get_db] = lambda: db_session_mock
22+
yield
23+
app.dependency_overrides[get_db] = None
24+
25+
26+
def test_successful_retrieval_of_blog_post(db_session_mock):
27+
blog = Blog(
28+
id=1,
29+
title="Sample Blog Post",
30+
excerpt="This is a sample excerpt.",
31+
content="Sample content for the blog post.",
32+
image_url="https://example.com/sample.jpg",
33+
created_at=datetime(2024, 7, 22, 12, 0, 0),
34+
updated_at=datetime(2024, 7, 22, 12, 30, 0),
35+
)
36+
37+
db_session_mock.query().filter().first.return_value = blog
38+
39+
response = client.get("/api/v1/blogs/1")
40+
41+
assert response.status_code == 200
42+
data = response.json()
43+
assert data["id"] == 1
44+
assert data["title"] == "Sample Blog Post"
45+
assert data["excerpt"] == "This is a sample excerpt."
46+
assert data["content"] == "Sample content for the blog post."
47+
assert data["image_url"] == "https://example.com/sample.jpg"
48+
assert data["created_at"] == "2024-07-22T12:00:00"
49+
assert data["updated_at"] == "2024-07-22T12:30:00"
50+
51+
52+
def test_invalid_blog_post_id():
53+
response = client.get("/api/v1/blogs/abc")
54+
55+
assert response.status_code == 422
56+
data = response.json()
57+
assert "detail" in data
58+
assert isinstance(data["detail"], list)
59+
assert any("msg" in error for error in data["detail"])

0 commit comments

Comments
 (0)