code coverage updates (#13245)

Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
OliverandMatthias Mair authored and GitHub committed 2026-11-11 20:50:13 +11:00
1 parent 2c8ec53ed2
commit 16309370ba
7 files changed
+71 -48

No files matched your search

+24 -21
View File
@@ -366,13 +366,12 @@ jobs:
mode: walltime
run: pytest ./src/performance --codspeed
coverage:
name: Tests - DB [SQLite] + Coverage ${{ matrix.python_version }}
sqlite:
name: Tests - DB [SQLite] ${{ matrix.python_version }}
runs-on: ubuntu-24.04
needs: ["code-style", "paths-filter"]
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true'
continue-on-error: true # continue if a step fails so that coverage gets pushed
strategy:
matrix:
python_version: [3.12, 3.14]
@@ -404,21 +403,8 @@ jobs:
run: |
invoke migrate --detect
python3 .github/scripts/check_migration_files.py
- name: Coverage Tests
run: invoke dev.test --check --coverage --translations
- name: Upload raw coverage to artifacts
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: coverage
path: .coverage
retention-days: 14
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
if: always()
with:
token: ${{ secrets.CODECOV_TOKEN }}
slug: inventree/InvenTree
flags: backend
- name: Run Tests
run: invoke dev.test --check --translations
performance:
name: Tests - Performance
@@ -458,7 +444,7 @@ jobs:
run: inv dev.test --pytest
postgres:
name: Tests - DB [PostgreSQL]
name: Tests - DB [PostgreSQL] + Coverage
runs-on: ubuntu-24.04
needs: ["code-style", "paths-filter"]
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true'
@@ -501,8 +487,25 @@ jobs:
dev-install: true
update: true
static: true
- name: Run Tests
run: invoke dev.test --check --translations
# Coverage is collected here (not on SQLite) so that tests which require
# select_for_update (concurrency / locking tests) contribute to coverage
- name: Coverage Tests
run: invoke dev.test --check --coverage --translations
- name: Upload raw coverage to artifacts
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: always()
with:
name: coverage
path: .coverage
include-hidden-files: true
retention-days: 14
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
if: always()
with:
token: ${{ secrets.CODECOV_TOKEN }}
slug: inventree/InvenTree
flags: backend
- name: Data Export Test
uses: $/.github/actions/migration
+4 -4
View File
@@ -1,5 +1,6 @@
ignore:
- "src/backend/InvenTree/**/test_migrations.py"
- "src/backend/InvenTree/**/migrations/**"
coverage:
status:
@@ -20,11 +21,10 @@ flag_management:
statuses:
- type: project
target: 85%
# Legacy flag - no longer uploaded (replaced by migrations-sqlite / -mysql / -postgresql).
# Disable carryforward so its stale report (incl. files since deleted) is dropped.
- name: migrations
carryforward: true
statuses:
- type: project
target: 38%
carryforward: false
- name: web
carryforward: true
statuses:
+1 -1
View File
@@ -119,7 +119,7 @@ unused-ignore-comment = "error"
[tool.coverage.run]
source = ["src/backend/InvenTree", "InvenTree"]
dynamic_context = "test_function"
omit = ["*/test_migrations.py"]
omit = ["*/test_migrations.py", "*/migrations/*"]
[tool.coverage.html]
show_contexts = true
@@ -454,6 +454,14 @@ MIDDLEWARE = ensure_removed(
MIDDLEWARE, 'oauth2_provider.middleware.OAuth2TokenMiddleware', 'MIDDLEWARE'
)
if TESTING:
# WhiteNoise scans the entire STATIC_ROOT whenever the middleware chain is loaded,
# which happens for every new test client (i.e. every test). No unit test relies on
# static files being served via HTTP, so the middleware is not loaded during testing.
MIDDLEWARE = [
m for m in MIDDLEWARE if m != 'whitenoise.middleware.WhiteNoiseMiddleware'
]
# Audit log configuration (django-auditlog)
# Ref: https://django-auditlog.readthedocs.io/en/latest/usage.html#settings
# Note: AUDITLOG_LOGENTRY_MODEL is a swappable model, and must not be changed once migrations have been applied
+5 -3
View File
@@ -296,9 +296,11 @@ def resolve_cid() -> Optional[str]:
"""Resolve the current correlation ID.
- Within a request: the request ID bound by django-structlog, so that audit entries
can be matched against log entries (and the X-Request-ID header) for the request.
Note: django-structlog uses the incoming 'X-Request-ID' header if provided
(e.g. by a reverse proxy), otherwise generates a new UUID.
can be matched against log entries (and the X-InvenTree-ReqId header) for the request.
Note: django-structlog generates a new UUID for each request - an incoming
'X-Request-ID' header is not trusted (TRUST_UPSTREAM_REQUEST_ID is not set),
so clients cannot spoof the correlation ID. The ID is returned to the client
in the 'X-InvenTree-ReqId' response header.
- Otherwise (e.g. background tasks, or if the structlog middleware is not installed):
a server-generated ID (see audit_request / audit_source).
"""
+28 -19
View File
@@ -458,29 +458,38 @@ class AuditAPITest(AuditTestMixin, InvenTreeAPITestCase):
self.assertEqual(data[0]['changes']['description'][1], 'Session edit')
def test_correlation_id(self):
"""The correlation ID matches the request ID (as used by structlog)."""
"""The correlation ID matches the request ID (as used by structlog).
The request ID is generated server-side - a client-supplied X-Request-ID
header is not trusted (django-structlog TRUST_UPSTREAM_REQUEST_ID is not set),
so a client cannot spoof the correlation ID recorded in the audit log.
"""
url = reverse('api-po-detail', kwargs={'pk': self.po.pk})
# Request ID provided by the client / reverse proxy
self.patch(url, {'description': 'One'}, HTTP_X_REQUEST_ID='req-abc-123')
entry = self.entries_for(self.po, action=AuditLogEntry.Action.UPDATE).get()
self.assertEqual(entry.cid, 'req-abc-123')
# Otherwise a unique ID is generated per request
self.patch(url, {'description': 'Two'})
self.patch(url, {'description': 'Three'})
cids = list(
self
.entries_for(self.po, action=AuditLogEntry.Action.UPDATE)
.exclude(cid='req-abc-123')
.values_list('cid', flat=True)
# A client-supplied request ID is ignored
response = self.patch(
url, {'description': 'One'}, HTTP_X_REQUEST_ID='req-abc-123'
)
self.assertEqual(len(cids), 2)
self.assertTrue(all(cids))
self.assertNotEqual(cids[0], cids[1])
entry = self.entries_for(self.po, action=AuditLogEntry.Action.UPDATE).get()
self.assertNotEqual(entry.cid, 'req-abc-123')
self.assertEqual(entry.cid, response['X-InvenTree-ReqId'])
# A unique ID is generated per request, matching the response header
req_ids = {entry.cid}
for description in ['Two', 'Three']:
response = self.patch(url, {'description': description})
req_ids.add(response['X-InvenTree-ReqId'])
cids = set(
self.entries_for(self.po, action=AuditLogEntry.Action.UPDATE).values_list(
'cid', flat=True
)
)
self.assertEqual(len(cids), 3)
self.assertEqual(cids, req_ids)
@override_settings(BASIC_AUTH_ENABLED=True)
def test_basic_auth_actor(self):
+1
View File
@@ -336,6 +336,7 @@ def main():
def builtin_apps():
"""Returns a list of installed apps."""
return [
'audit',
'build',
'common',
'company',