Skip to content
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
15 changes: 10 additions & 5 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ on:
pull_request:
branches: ["main", "dev"]
push:
branches: ["main", "dev"]
branches: ["main"]

permissions:
contents: read
pull-requests: write

jobs:
build:
Expand All @@ -28,7 +29,11 @@ jobs:
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with pylint
run: |
pylint $(git ls-files '*.py') --disable=C0301,R0801,R0903,R0913,R0917,W0622,W0707
# - name: Test with pytest
# run: |
# pytest
pylint $(git ls-files '*.py') --disable=C0301,R0801,R0903,R0913,R0917,W0622,W0707 --ignore-patterns=test_.*
- name: Test with pytest
run: |
pytest tests/ \
--cov=. \
--cov-report=term-missing \
--cov-report=xml:coverage.xml \
--cov-fail-under=80
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ repos:
rev: v4.0.4
hooks:
- id: pylint
args: ["--disable=C0301,E0401,R0801,R0903,R0913,R0917,W0622,W0707"]
args: ["--disable=C0301,E0401,R0801,R0903,R0913,R0917,W0622,W0707", "--ignore-patterns=test_.*"]
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ init-pre-commit: ## Init pre-commit
pre-commit clean
pre-commit install
pre-commit run --all-files

tests: ## Run tests in the Docker container
docker compose -f docker-compose-dev.yml run --rm test
24 changes: 24 additions & 0 deletions docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,27 @@ services:
container_name: rage-fastapi
ports:
- "8000:80"

coverage-report:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./htmlcov:/usr/share/nginx/html:ro
depends_on:
- test

test:
build: .
volumes:
- .:/app
environment:
- PYTHONPATH=/app
entrypoint: |
bash -c "
pip install coverage pytest pytest-cov &&
coverage run --source=. -m pytest tests/ -v &&
coverage report &&
coverage html &&
echo 'HTML Report generated at htmlcov/index.html'
"
15 changes: 7 additions & 8 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ class IdAndImageLink:
"""
id: int = Field(
description="Unique identifier for the model",
example=1,
json_schema_extra={"example": 1},
)
image_link: str = Field(
description="Link to an image of the model",
example="https://example.com/model_image.png"
json_schema_extra={"example": "https://example.com/model_image.png"},
)


Expand Down Expand Up @@ -51,15 +51,14 @@ class PedModel:
"""
name: str = Field(
description="Name of the ped model",
example="player_zero"
)
hash: str = Field(
description="Hexadecimal hash of the ped model",
example="0x92A27487"
json_schema_extra={"example": "0x92A27487"},
)
image_link: str = Field(
description="Link to an image of the ped model",
example="https://example.com/ped_image.png"
json_schema_extra={"example": "https://example.com/ped_image.png"},
)


Expand All @@ -70,13 +69,13 @@ class Weapon:
"""
name: str = Field(
description="Name of the weapon",
example="WEAPON_DAGGER",
json_schema_extra={"example": "WEAPON_DAGGER"},
)
hash: str = Field(
description="Hexadecimal hash of the weapon",
example="0x92A27487",
json_schema_extra={"example": "0x92A27487"},
)
type: str = Field(
description="Type of weapon (melee, handguns, smg, shotguns, assault rifles, machine guns, sniper rifles, heavy weapons, throwables, misc)",
example="melee",
json_schema_extra={"example": "melee"},
)
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ cffi==2.0.0
charset-normalizer==3.4.4
colorama==0.4.6
comm==0.2.3
coverage==7.6.1
debugpy==1.8.17
decorator==5.2.1
defusedxml==0.7.1
Expand Down Expand Up @@ -75,6 +76,8 @@ pydantic==2.12.5
pydantic-extra-types==2.11.0
pydantic_core==2.41.5
Pygments==2.19.2
pytest==8.3.3
pytest-cov==5.0.0
python-dateutil==2.9.0.post0
python-dotenv==1.2.1
python-json-logger==4.0.0
Expand Down
3 changes: 2 additions & 1 deletion services.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def get_model(model_name: str, filters) -> Union[List[BlipModel], List[BlipColor
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="No model founded")
return data


def get_model_with_id(id: int = None):
"""
Get a model with identifier.
Expand Down Expand Up @@ -74,7 +75,7 @@ def get_blip_models(filters = Depends(get_model_with_id)) -> List[BlipModel]:
"""
Get filtered or not blip models.
"""
return get_model("blip_colors", filters)
return get_model("blip_models", filters)


def get_markers(filters = Depends(get_model_with_id)) -> List[Marker]:
Expand Down
Empty file added tests/__init__.py
Empty file.
39 changes: 39 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""
This file contains unit tests for the application models.
"""


from models import IdAndImageLink, BlipColor, PedModel


def test_id_and_image_link_creation():
"""
Test the IdAndImageLink creation.
"""
obj = IdAndImageLink(id=42, image_link="https:/example.com/foo.png")
assert obj.id == 42
assert obj.image_link == "https:/example.com/foo.png"


def test_blip_color_inherits_id_and_image_link():
"""
Test the BlipColor creation.
"""
obj = BlipColor(id=1, image_link="https:/example.com/blip.png")
assert isinstance(obj, IdAndImageLink)
assert obj.id == 1
assert obj.image_link == "https:/example.com/blip.png"


def test_ped_model_creation():
"""
Test the PedModel creation.
"""
ped = PedModel(
name="player_zero",
hash="0x92A27487",
image_link="https://example.com/ped.png",
)
assert ped.name == "player_zero"
assert ped.hash == "0x92A27487"
assert ped.image_link == "https://example.com/ped.png"
166 changes: 166 additions & 0 deletions tests/test_services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
"""
This file contains unit tests for the application services.
"""

from fastapi import HTTPException
import json
import pytest
from unittest.mock import mock_open, patch

from services import (
get_model,
get_model_with_id,
get_model_with_name,
get_model_with_hash,
get_model_with_type,
get_ped_models,
get_blip_colors,
get_blip_models,
get_markers,
get_weapons,
)


SAMPLE_DATA = [
{"id": 1, "name": "A", "type": "melee"},
{"id": 2, "name": "B", "type": "rifle"},
]


def _mock_file(data):
"""
Mock the open function to return the provided data as a file-like object.
"""
return mock_open(read_data=json.dumps(data))


@patch("services.open", new_callable=lambda: _mock_file(SAMPLE_DATA))
def test_get_model_no_filters(mock_file):
"""
Test get_model without filters.
"""
result = get_model("weapons", {})
assert result == SAMPLE_DATA


@patch("services.open", new_callable=lambda: _mock_file(SAMPLE_DATA))
def test_get_model_with_id_filter(mock_file):
"""
Test get_model with id filter.
"""
result = get_model("weapons", {"id": 1})
assert result == [{"id": 1, "name": "A", "type": "melee"}]


@patch("services.open", new_callable=lambda: _mock_file(SAMPLE_DATA))
def test_get_model_with_type_filter(mock_file):
"""
Test get_model with type filter.
"""
result = get_model("weapons", {"type": "rifle"})
assert result == [{"id": 2, "name": "B", "type": "rifle"}]


@patch("services.open", side_effect=FileNotFoundError)
def test_get_model_file_not_found(mock_file):
"""
Test get_model when the file is not found.
"""
with pytest.raises(HTTPException) as exc:
get_model("weapons", {"id": 999})
assert exc.value.status_code == 404
assert exc.value.detail == "No model founded"


def test_get_model_with_id():
"""
Test get_model_with_id function.
"""
assert get_model_with_id(1) == {"id": 1}
assert get_model_with_id() == {}


def test_get_model_with_name():
"""
Test get_model_with_name function.
"""
assert get_model_with_name("A") == {"name": "A"}
assert get_model_with_name() == {}


def test_get_model_with_hash():
"""
Test get_model_with_hash function.
"""
assert get_model_with_hash("0x123") == {"hash": "0x123"}
assert get_model_with_hash() == {}


def test_get_model_with_type():
"""
Test get_model_with_type function.
"""
assert get_model_with_type("melee") == {"type": "melee"}
assert get_model_with_type() == {}


@patch("services.get_model")
def test_get_ped_models(mock_get_model):
name_filter = get_model_with_name("A")
hash_filter = get_model_with_hash("0x123")
mock_get_model.return_value = [{"name": "A", "hash": "0x123"}]
result = get_ped_models(name_filter=name_filter, hash_filter=hash_filter)
mock_get_model.assert_called_once_with(
"ped_models",
{"name": "A", "hash": "0x123"}
)
assert isinstance(result, list)


@patch("services.get_model")
def test_get_blip_colors(mock_get_model):
filters = get_model_with_id(1)
mock_get_model.return_value = [{"id": 1}]
result = get_blip_colors(filters)
mock_get_model.assert_called_once_with("blip_colors", filters)
assert isinstance(result, list)


@patch("services.get_model")
def test_get_blip_models(mock_get_model):
filters = get_model_with_id(1)
mock_get_model.return_value = [{"id": 1}]
result = get_blip_models(filters)
mock_get_model.assert_called_once_with("blip_models", filters)
assert isinstance(result, list)


@patch("services.get_model")
def test_get_markers(mock_get_model):
filters = get_model_with_id(1)
mock_get_model.return_value = [{"id": 1}]
result = get_markers(filters)
mock_get_model.assert_called_once_with("markers", filters)
assert isinstance(result, list)


@patch("services.get_model")
def test_get_weapons(mock_get_model):
name_filter = get_model_with_name("A")
hash_filter = get_model_with_hash("0x123")
type_filter = get_model_with_type("melee")
mock_get_model.return_value = [{"name": "A", "type": "melee"}]
result = get_weapons(
name_filter=name_filter,
hash_filter=hash_filter,
type_filter=type_filter
)
mock_get_model.assert_called_once_with(
"weapons",
{
**name_filter,
**hash_filter,
**type_filter
}
)
assert isinstance(result, list)