Skip to content

Add unit test and more coverage #56

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

Merged
merged 12 commits into from
Mar 20, 2024
Merged
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
14 changes: 13 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ jobs:
pkg-manager: poetry
- run: poetry run isort . --check
- run: poetry run black . --check
- run: poetry run mypy -p src.bss_web_file_server
- run: poetry run pylint src
- run: poetry run mypy -p src
verify-requirements:
executor: python
steps:
Expand All @@ -50,11 +51,22 @@ jobs:
- github-cli/setup:
version: 2.43.1
- run: gh release create << pipeline.git.tag >> -t << pipeline.git.tag >> --generate-notes
coverage:
executor: python
steps:
- checkout
- python/install-packages:
pkg-manager: poetry
- run: poetry run pytest --cov=src --cov-report=html --cov-fail-under=100
- store_artifacts:
path: htmlcov
workflows:
Build:
jobs:
- lint:
name: Lint
- coverage:
name: Coverage
- verify-requirements:
name: Verify requirements.txt
- python/test:
Expand Down
86 changes: 84 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ packages = [{include = "bss_web_file_server", from = "src"}]

[tool.poetry.dependencies]
python = "^3.12"
fastapi = {extras=["all"], version="0.110.0"}
uvicorn = {extras = ["standard"], version = "0.28.0"}
fastapi = {version="0.110.0", extras=["all"]}
uvicorn = {version = "0.28.0", extras = ["standard"]}
pillow = "10.2.0"
pillow-avif-plugin = "1.4.3"
python-multipart = "0.0.9"
Expand All @@ -25,6 +25,7 @@ types-Pillow = "10.2.0.20240311"
httpx = "^0.23.0"
pytest = "^8.0.0"
pytest-mock = "^3.10.0"
pytest-cov = "^4.1.0"
mypy = "1.9.0"

[tool.isort]
Expand Down
27 changes: 17 additions & 10 deletions src/bss_web_file_server/main.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
"""Main module for the FastAPI application."""

from contextlib import asynccontextmanager

from fastapi import FastAPI

from .routers import health, member, video
from .services.member import create_member_base_path
from .services.video import create_video_base_path
from .services.member import MemberService
from .services.video import VideoService

member_service = MemberService()
video_service = VideoService()


@asynccontextmanager
async def lifespan(api: FastAPI): # pylint: disable=unused-argument
"""Create the base paths for the video and member folders on startup."""
video_service.create_base_path()
member_service.create_base_path()
yield

app = FastAPI()

app = FastAPI(lifespan=lifespan)

app.include_router(health.router)
app.include_router(video.router)
app.include_router(member.router)


@app.on_event("startup")
async def startup_event():
"""Create the base paths for the video and member folders on startup."""
create_video_base_path()
create_member_base_path()
4 changes: 3 additions & 1 deletion src/bss_web_file_server/models/video.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
""" A module for storing the video model. """

from typing import Annotated
from uuid import UUID

from annotated_types import Len
from pydantic import BaseModel


class Video(BaseModel):
"""A class for storing video data."""

id: UUID
urls: list[str]
urls: Annotated[list[str], Len(min_length=1)]
7 changes: 5 additions & 2 deletions src/bss_web_file_server/routers/health.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
"""Health check endpoints."""

from fastapi import APIRouter
from fastapi.responses import PlainTextResponse

router = APIRouter(tags=["Health"])


@router.get("/health", response_model=str)
@router.get("/health", response_class=PlainTextResponse)
async def health():
"""Health check endpoint."""
return "UP"


@router.get("/ping", response_model=str)
@router.get("/ping", response_class=PlainTextResponse)
async def ping():
"""Ping check endpoint."""
return "PONG"
22 changes: 9 additions & 13 deletions src/bss_web_file_server/routers/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,10 @@
from fastapi import APIRouter, Response, UploadFile, status

from ..models.member import Member
from ..services.member import (
create_folder_structure,
create_profile_picture,
to_id_path,
update_symlink,
)
from ..services.member import MemberService

router = APIRouter(tags=["Member"], prefix="/api/v1/member")
service: MemberService = MemberService()


@router.post("", response_model=Member)
Expand All @@ -23,7 +19,7 @@ def create_member_folder(member: Member):
:param member: Member object
:return: 200 and the original member object
"""
create_folder_structure(member)
service.create_folder_structure(member)
return member


Expand All @@ -35,9 +31,9 @@ def update_member_folder(member: Member):
:param member: Member object
:return: 200 and the original member object
"""
if not to_id_path(member.id).exists():
if not service.to_id_path(member.id).exists():
return Response(status_code=status.HTTP_404_NOT_FOUND)
update_symlink(member)
service.update_symlink(member)
return member


Expand All @@ -52,15 +48,15 @@ async def upload_member_picture(member_id: UUID, file: UploadFile):
:param file: the image file
:return: 200 and the original member_id
"""
if not to_id_path(member_id).exists():
return Response(status_code=status.HTTP_404_NOT_FOUND)
# pylint: disable=duplicate-code
if not service.to_id_path(member_id).exists():
return Response(status_code=status.HTTP_404_NOT_FOUND)
if file.content_type is not None and not re.match("image/.+", file.content_type):
return Response(
content="Mime is not an image format",
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
# pylint: enable=duplicate-code
file_content = await file.read()
create_profile_picture(file_content, member_id)
# pylint: enable=duplicate-code
service.create_profile_picture(file_content, member_id)
return member_id
24 changes: 10 additions & 14 deletions src/bss_web_file_server/routers/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@

from fastapi import APIRouter, Response, UploadFile, status

from ..services.video import (
Video,
create_folder_structure,
create_thumbnails,
to_id_path,
update_symlinks,
)
from ..models.video import Video
from ..services.video import VideoService

router = APIRouter(tags=["Video"], prefix="/api/v1/video")
service: VideoService = VideoService()


@router.post("", response_model=Video)
Expand All @@ -23,7 +19,7 @@ def create_video_folder(video: Video):
:param video: Video object
:return: 200 and the original video object
"""
create_folder_structure(video)
service.create_folder_structure(video)
return video


Expand All @@ -35,9 +31,9 @@ def update_video_folder(video: Video):
:param video: Video object
:return: 200 and the original video object
"""
if not to_id_path(video.id).exists():
if not service.to_id_path(video.id).exists():
return Response(status_code=status.HTTP_404_NOT_FOUND)
update_symlinks(video)
service.update_symlinks(video)
return video


Expand All @@ -52,15 +48,15 @@ async def upload_video_poster(video_id: UUID, file: UploadFile):
:param file: the image file
:return: 200 and the original video_id
"""
if not to_id_path(video_id).exists():
return Response(status_code=status.HTTP_404_NOT_FOUND)
# pylint: disable=duplicate-code
if not service.to_id_path(video_id).exists():
return Response(status_code=status.HTTP_404_NOT_FOUND)
if file.content_type is not None and not re.match("image/.+", file.content_type):
return Response(
content="Mime is not an image format",
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
# pylint: enable=duplicate-code
file_content = await file.read()
create_thumbnails(file_content, video_id)
# pylint: enable=duplicate-code
service.create_thumbnails(file_content, video_id)
return video_id
Loading