Skip to content

Commit 02a717e

Browse files
committed
- src/schemas/movies.py
- CreateMovieRequest - MovieResponse - MovieSearchParams - MovieUpdateRequest - src/tests/test_movie_router.py - test_api_create_movie - test_api_get_movies_when_query_param_is_nothing - test_api_get_movies_when_query_param_is_not_none - test_api_get_movie - test_api_get_movie_when_movie_id_is_invalid - test_api_update_movie - test_api_update_movie_when_movie_id_is_invalid - test_api_delete_movie - test_api_delete_movie_when_movie_id_is_invalid - src/routers/movie_router.py - add movie_router(prefix="/movies", tags=["movies"]) - "/movies" - get(search or get movie list) - post(create movie) - "/movies/{movie_id}" - get(get movie) - patch(update movie) - delete(delete movie)
1 parent 590a20f commit 02a717e

File tree

3 files changed

+237
-0
lines changed

3 files changed

+237
-0
lines changed

src/routers/movie_router.py

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from typing import Annotated
2+
3+
from fastapi import APIRouter, HTTPException, Path, Query
4+
5+
from src.models.movies import MovieModel
6+
from src.schemas.movies import (
7+
CreateMovieRequest,
8+
MovieResponse,
9+
MovieSearchParams,
10+
MovieUpdateRequest,
11+
)
12+
13+
movie_router = APIRouter(prefix="/movies", tags=["movies"])
14+
15+
16+
@movie_router.post("", response_model=MovieResponse, status_code=201)
17+
async def create_movie(data: CreateMovieRequest) -> MovieModel:
18+
movie = MovieModel.create(**data.model_dump())
19+
return movie
20+
21+
22+
@movie_router.get("", response_model=list[MovieResponse], status_code=200)
23+
async def get_movies(query_params: Annotated[MovieSearchParams, Query()]) -> list[MovieModel]:
24+
valid_query = {key: value for key, value in query_params.model_dump().items() if value is not None}
25+
26+
if valid_query:
27+
return MovieModel.filter(**valid_query)
28+
29+
return MovieModel.all()
30+
31+
32+
@movie_router.get("/{movie_id}", response_model=MovieResponse, status_code=200)
33+
async def get_movie(movie_id: int = Path(gt=0)) -> MovieModel:
34+
movie = MovieModel.get(id=movie_id)
35+
if movie is None:
36+
raise HTTPException(status_code=404)
37+
return movie
38+
39+
40+
@movie_router.patch("/{movie_id}", response_model=MovieResponse, status_code=200)
41+
async def update_movie(data: MovieUpdateRequest, movie_id: int = Path(gt=0)) -> MovieModel:
42+
movie = MovieModel.get(id=movie_id)
43+
if movie is None:
44+
raise HTTPException(status_code=404)
45+
movie.update(**data.model_dump())
46+
return movie
47+
48+
49+
@movie_router.delete("/{movie_id}", status_code=204)
50+
async def delete_movie(movie_id: int = Path(gt=0)) -> None:
51+
movie = MovieModel.get(id=movie_id)
52+
if movie is None:
53+
raise HTTPException(status_code=404)
54+
movie.delete()

src/schemas/movies.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from typing import Annotated
2+
3+
from pydantic import BaseModel, Field
4+
5+
6+
class CreateMovieRequest(BaseModel):
7+
title: str
8+
playtime: int
9+
genre: list[str]
10+
11+
12+
class MovieResponse(BaseModel):
13+
id: int
14+
title: str
15+
playtime: int
16+
genre: list[str]
17+
18+
19+
class MovieSearchParams(BaseModel):
20+
title: str | None = None
21+
genre: str | None = None
22+
23+
24+
class MovieUpdateRequest(BaseModel):
25+
title: str | None = None
26+
playtime: Annotated[int, Field(gt=0)] | None = None
27+
genre: list[str] | None = None

src/tests/test_movie_router.py

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import httpx
2+
from fastapi import status
3+
4+
from main import app
5+
from src.models.movies import MovieModel
6+
7+
8+
async def test_api_create_movie() -> None:
9+
# when
10+
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
11+
response = await client.post(
12+
"/movies",
13+
json={
14+
"title": (title := "test"),
15+
"playtime": (playtime := 240),
16+
"genre": (genre := ["SF", "Romance", "Action"]),
17+
},
18+
)
19+
20+
# then
21+
assert response.status_code == status.HTTP_201_CREATED
22+
response_json = response.json()
23+
assert response_json["title"] == title
24+
assert response_json["playtime"] == playtime
25+
assert response_json["genre"] == genre
26+
27+
28+
async def test_api_get_movies_when_query_param_is_nothing() -> None:
29+
# given
30+
MovieModel.create_dummy()
31+
32+
# when
33+
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
34+
response = await client.get("/movies")
35+
36+
# then
37+
assert response.status_code == status.HTTP_200_OK
38+
response_json = response.json()
39+
assert len(response_json) == 10
40+
assert response_json[0]["id"] == MovieModel._data[0].id
41+
assert response_json[0]["title"] == MovieModel._data[0].title
42+
assert response_json[0]["playtime"] == MovieModel._data[0].playtime
43+
assert response_json[0]["genre"] == MovieModel._data[0].genre
44+
45+
46+
async def test_api_get_movies_when_query_param_is_not_none() -> None:
47+
# given
48+
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
49+
await client.post(
50+
"/movies",
51+
json={"title": (title := "test"), "playtime": 240, "genre": (genre := ["SF", "Romance", "Action"])},
52+
)
53+
54+
# when
55+
response = await client.get("/movies", params={"title": title, "genre": genre[0]})
56+
57+
# then
58+
assert response.status_code == status.HTTP_200_OK
59+
response_json = response.json()
60+
assert len(response_json) == 1
61+
assert response_json[0]["title"] == title
62+
assert response_json[0]["genre"] == genre
63+
64+
65+
async def test_api_get_movie() -> None:
66+
# given
67+
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
68+
create_response = await client.post(
69+
"/movies",
70+
json={
71+
"title": (title := "test"),
72+
"playtime": (playtime := 240),
73+
"genre": (genre := ["SF", "Romance", "Action"]),
74+
},
75+
)
76+
movie_id = create_response.json()["id"]
77+
# when
78+
response = await client.get(f"/movies/{movie_id}")
79+
80+
# then
81+
assert response.status_code == status.HTTP_200_OK
82+
response_json = response.json()
83+
assert response_json["title"] == title
84+
assert response_json["playtime"] == playtime
85+
assert response_json["genre"] == genre
86+
87+
88+
async def test_api_get_movie_when_movie_id_is_invalid() -> None:
89+
# when
90+
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
91+
response = await client.get("/movies/1232131311")
92+
93+
# then
94+
assert response.status_code == status.HTTP_404_NOT_FOUND
95+
96+
97+
async def test_api_update_movie() -> None:
98+
# when
99+
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
100+
create_response = await client.post(
101+
"/movies", json={"title": "test", "playtime": 240, "genre": ["SF", "Romance", "Action"]}
102+
)
103+
movie_id = create_response.json()["id"]
104+
105+
# when
106+
response = await client.patch(
107+
f"/movies/{movie_id}",
108+
json={
109+
"title": (updated_title := "updated_title"),
110+
"playtime": (updated_playtime := 180),
111+
"genre": (updated_genre := ["Fantasy", "Romance", "Adventure"]),
112+
},
113+
)
114+
115+
# then
116+
assert response.status_code == status.HTTP_200_OK
117+
response_json = response.json()
118+
assert response_json["title"] == updated_title
119+
assert response_json["playtime"] == updated_playtime
120+
assert response_json["genre"] == updated_genre
121+
122+
123+
async def test_api_update_movie_when_movie_id_is_invalid() -> None:
124+
# when
125+
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
126+
response = await client.patch(
127+
"/movies/1232131311",
128+
json={"title": "updated_title", "playtime": 180, "genre": ["Fantasy", "Romance", "Adventure"]},
129+
)
130+
131+
# then
132+
assert response.status_code == status.HTTP_404_NOT_FOUND
133+
134+
135+
async def test_api_delete_movie() -> None:
136+
# when
137+
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
138+
create_response = await client.post(
139+
"/movies", json={"title": "test", "playtime": 240, "genre": ["SF", "Romance", "Action"]}
140+
)
141+
movie_id = create_response.json()["id"]
142+
143+
# when
144+
response = await client.delete(f"/movies/{movie_id}")
145+
146+
# then
147+
assert response.status_code == status.HTTP_204_NO_CONTENT
148+
149+
150+
async def test_api_delete_movie_when_movie_id_is_invalid() -> None:
151+
# when
152+
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
153+
response = await client.delete("/movies/1232131311")
154+
155+
# then
156+
assert response.status_code == status.HTTP_404_NOT_FOUND

0 commit comments

Comments
 (0)