Skip to content

Commit

Permalink
Add more test coverage for backend (#8048)
Browse files Browse the repository at this point in the history
* add test for asset redirect

* fully cover spa_bundler

* test token api

* check without beeing authed

* not possible to be reached - no cover

* remove unneeded except

* fully test group apis

* move ignore

* add tests for admin site

* add full admin testing

* use output as ref

* ignore admin edge-case

* test display name settings

* refactor admin test

* add more admin testing

* fix tests assertation

* fix assertations

* add test for importer admin

* remove old test for function that will not be re-added for now
see #8018 (comment)

* Add stock detail with wrong pk

* add a few stock tests
  • Loading branch information
matmair authored Sep 2, 2024
1 parent a102e17 commit 79affbe
Show file tree
Hide file tree
Showing 12 changed files with 208 additions and 47 deletions.
30 changes: 29 additions & 1 deletion src/backend/InvenTree/InvenTree/unit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@

from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, Permission
from django.db import connections
from django.db import connections, models
from django.http.response import StreamingHttpResponse
from django.test import TestCase
from django.test.utils import CaptureQueriesContext
from django.urls import reverse

from djmoney.contrib.exchange.models import ExchangeBackend, Rate
from rest_framework.test import APITestCase
Expand Down Expand Up @@ -486,3 +487,30 @@ def process_csv(
def assertDictContainsSubset(self, a, b):
"""Assert that dictionary 'a' is a subset of dictionary 'b'."""
self.assertEqual(b, b | a)


class AdminTestCase(InvenTreeAPITestCase):
"""Tests for the admin interface integration."""

superuser = True

def helper(self, model: type[models.Model], model_kwargs=None):
"""Test the admin URL."""
if model_kwargs is None:
model_kwargs = {}

# Add object
obj = model.objects.create(**model_kwargs)
app_app, app_mdl = model._meta.app_label, model._meta.model_name

# 'Test listing
response = self.get(reverse(f'admin:{app_app}_{app_mdl}_changelist'))
self.assertEqual(response.status_code, 200)

# Test change view
response = self.get(
reverse(f'admin:{app_app}_{app_mdl}_change', kwargs={'object_id': obj.pk})
)
self.assertEqual(response.status_code, 200)

return obj
41 changes: 32 additions & 9 deletions src/backend/InvenTree/importer/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,27 @@

from django.core.files.base import ContentFile

from importer.models import DataImportSession
from InvenTree.unit_test import InvenTreeTestCase
from importer.models import DataImportRow, DataImportSession
from InvenTree.unit_test import AdminTestCase, InvenTreeTestCase


class ImporterTest(InvenTreeTestCase):
class ImporterMixin:
"""Helpers for import tests."""

def helper_file(self):
"""Return test data."""
fn = os.path.join(os.path.dirname(__file__), 'test_data', 'companies.csv')

with open(fn, encoding='utf-8') as input_file:
data = input_file.read()
return data

def helper_content(self):
"""Return content file."""
return ContentFile(self.helper_file(), 'companies.csv')


class ImporterTest(ImporterMixin, InvenTreeTestCase):
"""Basic tests for file imports."""

def test_import_session(self):
Expand All @@ -17,13 +33,8 @@ def test_import_session(self):

n = Company.objects.count()

fn = os.path.join(os.path.dirname(__file__), 'test_data', 'companies.csv')

with open(fn, encoding='utf-8') as input_file:
data = input_file.read()

session = DataImportSession.objects.create(
data_file=ContentFile(data, 'companies.csv'), model_type='company'
data_file=self.helper_content(), model_type='company'
)

session.extract_columns()
Expand Down Expand Up @@ -61,3 +72,15 @@ def test_import_session(self):

def test_field_defaults(self):
"""Test default field values."""


class AdminTest(ImporterMixin, AdminTestCase):
"""Tests for the admin interface integration."""

def test_admin(self):
"""Test the admin URL."""
session = self.helper(
model=DataImportSession,
model_kwargs={'data_file': self.helper_content(), 'model_type': 'company'},
)
self.helper(model=DataImportRow, model_kwargs={'session_id': session.id})
10 changes: 9 additions & 1 deletion src/backend/InvenTree/machine/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from rest_framework import serializers

from InvenTree.unit_test import InvenTreeAPITestCase
from InvenTree.unit_test import AdminTestCase, InvenTreeAPITestCase
from machine.machine_type import BaseDriver, BaseMachineType, MachineStatus
from machine.machine_types.label_printer import LabelPrinterBaseDriver
from machine.models import MachineConfig
Expand Down Expand Up @@ -309,3 +309,11 @@ def test_print_label(self):
},
expected_code=400,
)


class AdminTest(AdminTestCase):
"""Tests for the admin interface integration."""

def test_admin(self):
"""Test the admin URL."""
self.helper(model=MachineConfig)
28 changes: 9 additions & 19 deletions src/backend/InvenTree/report/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import report.models as report_models
from build.models import Build
from common.models import Attachment, InvenTreeSetting
from InvenTree.unit_test import InvenTreeAPITestCase
from InvenTree.unit_test import AdminTestCase, InvenTreeAPITestCase
from order.models import ReturnOrder, SalesOrder
from plugin.registry import registry
from report.models import LabelTemplate, ReportTemplate
Expand Down Expand Up @@ -580,24 +580,6 @@ def test_print(self):
Attachment.objects.filter(model_id=item.pk, model_type='stockitem').exists()
)

return
# TODO @matmair - Re-add this test after https://github.com/inventree/InvenTree/pull/7074/files#r1600694356 is resolved
# Change the setting, now the test report should be attached automatically
InvenTreeSetting.set_setting('REPORT_ATTACH_TEST_REPORT', True, None)

response = self.post(
url, {'template': template.pk, 'items': [item.pk]}, expected_code=201
)

# There should be a link to the generated PDF
self.assertEqual(response.data['output'].startswith('/media/report/'), True)

# Check that a report has been uploaded
attachment = Attachment.objects.filter(
model_id=item.pk, model_type='stockitem'
).first()
self.assertIsNotNone(attachment)

def test_mdl_build(self):
"""Test the Build model."""
self.run_print_test(Build, 'build', label=False)
Expand All @@ -609,3 +591,11 @@ def test_mdl_returnorder(self):
def test_mdl_salesorder(self):
"""Test the SalesOrder model."""
self.run_print_test(SalesOrder, 'salesorder', label=False)


class AdminTest(AdminTestCase):
"""Tests for the admin interface integration."""

def test_admin(self):
"""Test the admin URL."""
self.helper(model=ReportTemplate)
11 changes: 11 additions & 0 deletions src/backend/InvenTree/stock/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1527,6 +1527,11 @@ class StocktakeTest(StockAPITestCase):

def test_action(self):
"""Test each stocktake action endpoint, for validation."""
target = {
'api-stock-count': '10.00000',
'api-stock-add': '10.00000',
'api-stock-remove': '10.00000',
}
for endpoint in ['api-stock-count', 'api-stock-add', 'api-stock-remove']:
url = reverse(endpoint)

Expand Down Expand Up @@ -1585,6 +1590,12 @@ def test_action(self):
status_code=status.HTTP_400_BAD_REQUEST,
)

# Valid POST
data = {'items': [{'pk': 1234, 'quantity': 10}]}
response = self.post(url, data, expected_code=201)
self.assertEqual(response.data['items'][0]['pk'], 1234)
self.assertEqual(response.data['items'][0]['quantity'], target[endpoint])

def test_transfer(self):
"""Test stock transfers."""
stock_item = StockItem.objects.get(pk=1234)
Expand Down
5 changes: 5 additions & 0 deletions src/backend/InvenTree/stock/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ def test_basic_info(self):
for act in actions:
self.assertIn(act, html)

# Check with a wrong pk
response = self.client.get(reverse('stock-item-detail', kwargs={'pk': 99}))
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse('stock-index'))


class StockOwnershipTest(StockViewTestCase):
"""Tests for stock ownership views."""
Expand Down
10 changes: 9 additions & 1 deletion src/backend/InvenTree/stock/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from build.models import Build
from common.models import InvenTreeSetting
from company.models import Company
from InvenTree.unit_test import InvenTreeTestCase
from InvenTree.unit_test import AdminTestCase, InvenTreeTestCase
from order.models import SalesOrder
from part.models import Part, PartTestTemplate
from stock.status_codes import StockHistoryCode
Expand Down Expand Up @@ -1347,3 +1347,11 @@ def test_icon(self):
loc.location_type = None
loc.save()
self.assertEqual(loc.icon, '')


class AdminTest(AdminTestCase):
"""Tests for the admin interface integration."""

def test_admin(self):
"""Test the admin URL."""
self.helper(model=StockLocationType)
13 changes: 5 additions & 8 deletions src/backend/InvenTree/users/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,13 +175,10 @@ class GroupMixin:
def get_serializer(self, *args, **kwargs):
"""Return serializer instance for this endpoint."""
# Do we wish to include extra detail?
try:
params = self.request.query_params
kwargs['permission_detail'] = InvenTree.helpers.str2bool(
params.get('permission_detail', None)
)
except AttributeError:
pass
params = self.request.query_params
kwargs['permission_detail'] = InvenTree.helpers.str2bool(
params.get('permission_detail', None)
)
kwargs['context'] = self.get_serializer_context()
return self.serializer_class(*args, **kwargs)

Expand Down Expand Up @@ -346,7 +343,7 @@ def get(self, request, *args, **kwargs):
return Response(data)

else:
raise exceptions.NotAuthenticated()
raise exceptions.NotAuthenticated() # pragma: no cover


class TokenListView(DestroyAPIView, ListAPI):
Expand Down
2 changes: 1 addition & 1 deletion src/backend/InvenTree/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def token(self):
"""
# If the token has not yet been saved, return the raw key
if self.pk is None:
return self.key
return self.key # pragma: no cover

M = len(self.key) - 20

Expand Down
44 changes: 42 additions & 2 deletions src/backend/InvenTree/users/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,21 @@ def test_group_api(self):
self.assertEqual(len(response.data), Group.objects.count())

# Check detail URL
pk = response.data[0]['pk']
response = self.get(
reverse('api-group-detail', kwargs={'pk': response.data[0]['pk']}),
expected_code=200,
reverse('api-group-detail', kwargs={'pk': pk}), expected_code=200
)
self.assertIn('name', response.data)
self.assertNotIn('permissions', response.data)

# Check more detailed URL
response = self.get(
reverse('api-group-detail', kwargs={'pk': pk}),
data={'permission_detail': True},
expected_code=200,
)
self.assertIn('name', response.data)
self.assertIn('permissions', response.data)

def test_logout(self):
"""Test api logout endpoint."""
Expand Down Expand Up @@ -208,3 +217,34 @@ def test_buildin_token(self):
)
self.assertIn('key', response.data)
self.assertTrue(response.data['key'].startswith('inv-'))

def test_token_api(self):
"""Test the token API."""
url = reverse('api-token-list')
response = self.get(url, expected_code=200)
self.assertEqual(response.data, [])

# Get token
response = self.get(reverse('api-token'), expected_code=200)
self.assertIn('token', response.data)

# Now there should be one token
response = self.get(url, expected_code=200)
self.assertEqual(len(response.data), 1)
self.assertEqual(response.data[0]['active'], True)
self.assertEqual(response.data[0]['revoked'], False)
self.assertEqual(response.data[0]['in_use'], False)
expected_day = str(
datetime.datetime.now().date() + datetime.timedelta(days=365)
)
self.assertEqual(response.data[0]['expiry'], expected_day)

# Destroy token
self.delete(
reverse('api-token-detail', kwargs={'pk': response.data[0]['id']}),
expected_code=204,
)

# Get token without auth (should fail)
self.client.logout()
self.get(reverse('api-token'), expected_code=401)
38 changes: 37 additions & 1 deletion src/backend/InvenTree/users/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from django.test import TestCase, tag
from django.urls import reverse

from InvenTree.unit_test import InvenTreeAPITestCase, InvenTreeTestCase
from common.settings import set_global_setting
from InvenTree.unit_test import AdminTestCase, InvenTreeAPITestCase, InvenTreeTestCase
from users.models import ApiToken, Owner, RuleSet


Expand Down Expand Up @@ -270,6 +271,29 @@ def test_token(self):
)
self.assertEqual(response['username'], self.username)

def test_display_name(self):
"""Test the display name for the owner."""
owner = Owner.get_owner(self.user)
self.assertEqual(owner.name(), 'testuser')
self.assertEqual(str(owner), 'testuser (user)')

# Change setting
set_global_setting('DISPLAY_FULL_NAMES', True)
self.user.first_name = 'first'
self.user.last_name = 'last'
self.user.save()
owner = Owner.get_owner(self.user)

# Now first / last should be used
self.assertEqual(owner.name(), 'first last')
self.assertEqual(str(owner), 'first last (user)')

# Reset
set_global_setting('DISPLAY_FULL_NAMES', False)
self.user.first_name = ''
self.user.last_name = ''
self.user.save()


class MFALoginTest(InvenTreeAPITestCase):
"""Some simplistic tests to ensure that MFA is working."""
Expand Down Expand Up @@ -313,3 +337,15 @@ def test_api(self):
# Wrong login should not work
auth_data['password'] = 'wrong'
self.post(login_url, auth_data, expected_code=401)


class AdminTest(AdminTestCase):
"""Tests for the admin interface integration."""

def test_admin(self):
"""Test the admin URL."""
my_token = self.helper(
model=ApiToken, model_kwargs={'user': self.user, 'name': 'test-token'}
)
# Additionally test str fnc
self.assertEqual(str(my_token), my_token.token)
Loading

0 comments on commit 79affbe

Please sign in to comment.