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
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,16 @@ jobs:
- name: Run django checks
run: uv run python manage.py check

- name: Run tests
run: uv run python manage.py test
- name: Run tests with coverage
run: uv run pytest

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
files: ./coverage.xml
fail_ci_if_error: false
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

build-image:
needs: build
Expand Down
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ server: .env
db:
@$(MNG) migrate

.PHONY: test
test:
@uv run pytest

.PHONY: test-cov
test-cov:
@uv run pytest --cov-report=term --cov-report=html

.PHONY: test-cov-xml
test-cov-xml:
@uv run pytest --cov-report=xml
Comment on lines +17 to +21
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test-cov and test-cov-xml targets override the coverage report settings defined in pyproject.toml (lines 30-32). The pyproject.toml already configures --cov-report=term-missing, --cov-report=html, and --cov-report=xml in the addopts section. These Makefile targets should either:

  1. Remove the redundant --cov-report flags and rely on pyproject.toml defaults, or
  2. Use --no-cov to disable coverage and then re-enable specific reports if customization is needed.

The current implementation may produce duplicate reports.

Suggested change
@uv run pytest --cov-report=term --cov-report=html
.PHONY: test-cov-xml
test-cov-xml:
@uv run pytest --cov-report=xml
@uv run pytest
.PHONY: test-cov-xml
test-cov-xml:
@uv run pytest

Copilot uses AI. Check for mistakes.

requirements.txt: uv.lock
@uv export --frozen --output-file=$@

Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# I Don't Need It

[![CI](https://github.com/recursive-one/idontneedit/actions/workflows/ci.yml/badge.svg)](https://github.com/recursive-one/idontneedit/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/recursive-one/idontneedit/graph/badge.svg?token=RSZ1IAQHK6)](https://codecov.io/gh/recursive-one/idontneedit)
[![Python](https://img.shields.io/badge/python-3.13-blue.svg)](https://www.python.org/downloads/)
[![Django](https://img.shields.io/badge/django-6.0-green.svg)](https://www.djangoproject.com/)
2 changes: 1 addition & 1 deletion ansible/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@

- name: add Docker APT repository (Ubuntu)
ansible.builtin.apt_repository:
repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ansible_lsb.codename }} stable"
repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ansible_facts['lsb']['codename'] }} stable"
filename: docker
state: present

Expand Down
Empty file added config/tests/__init__.py
Empty file.
47 changes: 47 additions & 0 deletions config/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import pytest
from django.urls import reverse
from unittest.mock import patch

# Use pytest-django's built-in `client` fixture


@pytest.mark.django_db
class TestHealthCheck:
"""Test suite for health check endpoint."""

def test_health_check_success(self, client):
"""Test that health check returns 200 when database is connected."""
response = client.get(reverse("health_check"))

assert response.status_code == 200
assert response["Content-Type"] == "application/json"

data = response.json()
assert data["status"] == "healthy"
assert data["database"] == "connected"

def test_health_check_database_failure(self, client):
"""Test that health check returns 503 when database connection fails."""
with patch("config.views.connection") as mock_connection:
mock_connection.cursor.return_value.__enter__.return_value.execute.side_effect = Exception(
"Database connection failed"
)

response = client.get(reverse("health_check"))

assert response.status_code == 503

data = response.json()
assert data["status"] == "unhealthy"
assert data["database"] == "disconnected"

def test_health_check_only_allows_get(self, client):
"""Test that health check endpoint only accepts GET requests."""
response = client.post(reverse("health_check"))
assert response.status_code == 405

response = client.put(reverse("health_check"))
assert response.status_code == 405

response = client.delete(reverse("health_check"))
assert response.status_code == 405
42 changes: 42 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,46 @@ dependencies = [
dev = [
"black>=25.11.0",
"pre-commit>=4.5.0",
"pytest>=8.3.4",
"pytest-django>=4.9.0",
"pytest-cov>=6.0.0",
]

[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "config.settings"
python_files = ["test_*.py", "*_test.py", "tests.py"]
addopts = [
"--strict-markers",
"--strict-config",
"--cov=config",
"--cov-report=term-missing",
"--cov-report=html",
"--cov-report=xml",
]

[tool.coverage.run]
source = ["."]
Comment on lines +29 to +36
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a conflicting coverage configuration. Line 29 specifies --cov=config which limits coverage to the config module, but line 36 specifies source = ["."] which tells coverage to measure the entire project. This can lead to inconsistent results. Consider either:

  1. Using --cov=. and keeping source = ["."], or
  2. Removing the source setting from [tool.coverage.run] and relying on the --cov=config argument.

For comprehensive coverage of the entire project, option 1 is recommended.

Copilot uses AI. Check for mistakes.
omit = [
"*/tests/*",
"*/test_*.py",
"*_test.py",
"*/migrations/*",
"manage.py",
"*/wsgi.py",
"*/asgi.py",
"*/__pycache__/*",
"*/.venv/*",
"*/venv/*",
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod",
]
87 changes: 86 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,65 @@ click==8.3.1 \
colorama==0.4.6 ; sys_platform == 'win32' \
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
# via click
# via
# click
# pytest
coverage==7.12.0 \
--hash=sha256:01d24af36fedda51c2b1aca56e4330a3710f83b02a5ff3743a6b015ffa7c9384 \
--hash=sha256:04a79245ab2b7a61688958f7a855275997134bc84f4a03bc240cf64ff132abf6 \
--hash=sha256:099d11698385d572ceafb3288a5b80fe1fc58bf665b3f9d362389de488361d3d \
--hash=sha256:09a86acaaa8455f13d6a99221d9654df249b33937b4e212b4e5a822065f12aa7 \
--hash=sha256:159d50c0b12e060b15ed3d39f87ed43d4f7f7ad40b8a534f4dd331adbb51104a \
--hash=sha256:172cf3a34bfef42611963e2b661302a8931f44df31629e5b1050567d6b90287d \
--hash=sha256:24cff9d1f5743f67db7ba46ff284018a6e9aeb649b67aa1e70c396aa1b7cb23c \
--hash=sha256:33baadc0efd5c7294f436a632566ccc1f72c867f82833eb59820ee37dc811c6f \
--hash=sha256:40c867af715f22592e0d0fb533a33a71ec9e0f73a6945f722a0c85c8c1cbe3a2 \
--hash=sha256:459443346509476170d553035e4a3eed7b860f4fe5242f02de1010501956ce87 \
--hash=sha256:47324fffca8d8eae7e185b5bb20c14645f23350f870c1649003618ea91a78941 \
--hash=sha256:473dc45d69694069adb7680c405fb1e81f60b2aff42c81e2f2c3feaf544d878c \
--hash=sha256:4c589361263ab2953e3c4cd2a94db94c4ad4a8e572776ecfbad2389c626e4507 \
--hash=sha256:5560c7e0d82b42eb1951e4f68f071f8017c824ebfd5a6ebe42c60ac16c6c2434 \
--hash=sha256:583f9adbefd278e9de33c33d6846aa8f5d164fa49b47144180a0e037f0688bb9 \
--hash=sha256:58c1c6aa677f3a1411fe6fb28ec3a942e4f665df036a3608816e0847fad23296 \
--hash=sha256:5b3c889c0b8b283a24d721a9eabc8ccafcfc3aebf167e4cd0d0e23bf8ec4e339 \
--hash=sha256:5ea5a9f7dc8877455b13dd1effd3202e0bca72f6f3ab09f9036b1bcf728f69ac \
--hash=sha256:68b0d0a2d84f333de875666259dadf28cc67858bc8fd8b3f1eae84d3c2bec455 \
--hash=sha256:6e1a8c066dabcde56d5d9fed6a66bc19a2883a3fe051f0c397a41fc42aedd4cc \
--hash=sha256:73f9e7fbd51a221818fd11b7090eaa835a353ddd59c236c57b2199486b116c6d \
--hash=sha256:76336c19a9ef4a94b2f8dc79f8ac2da3f193f625bb5d6f51a328cd19bfc19933 \
--hash=sha256:79a44421cd5fba96aa57b5e3b5a4d3274c449d4c622e8f76882d76635501fd13 \
--hash=sha256:7c1059b600aec6ef090721f8f633f60ed70afaffe8ecab85b59df748f24b31fe \
--hash=sha256:874fe69a0785d96bd066059cd4368022cebbec1a8958f224f0016979183916e6 \
--hash=sha256:8787b0f982e020adb732b9f051f3e49dd5054cebbc3f3432061278512a2b1360 \
--hash=sha256:8bb5b894b3ec09dcd6d3743229dc7f2c42ef7787dc40596ae04c0edda487371e \
--hash=sha256:907e0df1b71ba77463687a74149c6122c3f6aac56c2510a5d906b2f368208560 \
--hash=sha256:90d58ac63bc85e0fb919f14d09d6caa63f35a5512a2205284b7816cafd21bb03 \
--hash=sha256:9157a5e233c40ce6613dead4c131a006adfda70e557b6856b97aceed01b0e27a \
--hash=sha256:91b810a163ccad2e43b1faa11d70d3cf4b6f3d83f9fd5f2df82a32d47b648e0d \
--hash=sha256:950411f1eb5d579999c5f66c62a40961f126fc71e5e14419f004471957b51508 \
--hash=sha256:9b57e2d0ddd5f0582bae5437c04ee71c46cd908e7bc5d4d0391f9a41e812dd12 \
--hash=sha256:a00594770eb715854fb1c57e0dea08cce6720cfbc531accdb9850d7c7770396c \
--hash=sha256:a1c59b7dc169809a88b21a936eccf71c3895a78f5592051b1af8f4d59c2b4f92 \
--hash=sha256:aa7d48520a32cb21c7a9b31f81799e8eaec7239db36c3b670be0fa2403828d1d \
--hash=sha256:b1aab7302a87bafebfe76b12af681b56ff446dc6f32ed178ff9c092ca776e6bc \
--hash=sha256:b2089cc445f2dc0af6f801f0d1355c025b76c24481935303cf1af28f636688f0 \
--hash=sha256:b365adc70a6936c6b0582dc38746b33b2454148c02349345412c6e743efb646d \
--hash=sha256:bc13baf85cd8a4cfcf4a35c7bc9d795837ad809775f782f697bf630b7e200211 \
--hash=sha256:bcec6f47e4cb8a4c2dc91ce507f6eefc6a1b10f58df32cdc61dff65455031dfc \
--hash=sha256:c406a71f544800ef7e9e0000af706b88465f3573ae8b8de37e5f96c59f689ad1 \
--hash=sha256:c87395744f5c77c866d0f5a43d97cc39e17c7f1cb0115e54a2fe67ca75c5d14d \
--hash=sha256:ca8ecfa283764fdda3eae1bdb6afe58bf78c2c3ec2b2edcb05a671f0bba7b3f9 \
--hash=sha256:ccf3b2ede91decd2fb53ec73c1f949c3e034129d1e0b07798ff1d02ea0c8fa4a \
--hash=sha256:ce61969812d6a98a981d147d9ac583a36ac7db7766f2e64a9d4d059c2fe29d07 \
--hash=sha256:d6c2e26b481c9159c2773a37947a9718cfdc58893029cdfb177531793e375cfc \
--hash=sha256:d7e0d0303c13b54db495eb636bc2465b2fb8475d4c8bcec8fe4b5ca454dfbae8 \
--hash=sha256:e0f483ab4f749039894abaf80c2f9e7ed77bbf3c737517fb88c8e8e305896a17 \
--hash=sha256:e71bba6a40883b00c6d571599b4627f50c360b3d0d02bfc658168936be74027b \
--hash=sha256:e84da3a0fd233aeec797b981c51af1cabac74f9bd67be42458365b30d11b5291 \
--hash=sha256:f7ba9da4726e446d8dd8aae5a6cd872511184a5d861de80a86ef970b5dacce3e \
--hash=sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c \
--hash=sha256:fdba9f15849534594f60b47c9a30bc70409b54947319a7c4fd0e8e3d8d2f355d
# via pytest-cov
distlib==0.4.0 \
--hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \
--hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d
Expand All @@ -51,6 +109,10 @@ identify==2.6.15 \
--hash=sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757 \
--hash=sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf
# via pre-commit
iniconfig==2.3.0 \
--hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \
--hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12
# via pytest
mypy-extensions==1.1.0 \
--hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \
--hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558
Expand All @@ -65,6 +127,7 @@ packaging==25.0 \
# via
# black
# gunicorn
# pytest
pathspec==0.12.1 \
--hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \
--hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712
Expand All @@ -75,6 +138,12 @@ platformdirs==4.5.0 \
# via
# black
# virtualenv
pluggy==1.6.0 \
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
# via
# pytest
# pytest-cov
pre-commit==4.5.0 \
--hash=sha256:25e2ce09595174d9c97860a95609f9f852c0614ba602de3561e267547f2335e1 \
--hash=sha256:dc5a065e932b19fc1d4c653c6939068fe54325af8e741e74e88db4d28a4dd66b
Expand Down Expand Up @@ -102,6 +171,22 @@ psycopg-binary==3.2.13 ; implementation_name != 'pypy' \
--hash=sha256:f26f7009375cf1e92180e5c517c52da1054f7e690dde90e0ed00fa8b5736bcd4 \
--hash=sha256:fae933e4564386199fc54845d85413eedb49760e0bcd2b621fde2dd1825b99b3
# via psycopg
pygments==2.19.2 \
--hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \
--hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b
# via pytest
pytest==9.0.1 \
--hash=sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8 \
--hash=sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad
# via
# pytest-cov
# pytest-django
pytest-cov==7.0.0 \
--hash=sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1 \
--hash=sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861
pytest-django==4.11.1 \
--hash=sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10 \
--hash=sha256:a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991
pytokens==0.3.0 \
--hash=sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a \
--hash=sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3
Expand Down
Loading