2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-05-01 04:56:45 +00:00

Add more test coverage for backend (#8048)

* 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 https://github.com/inventree/InvenTree/pull/8018#discussion_r1734011050

* Add stock detail with wrong pk

* add a few stock tests
This commit is contained in:
Matthias Mair 2024-09-02 02:13:39 +02:00 committed by GitHub
parent a102e178d1
commit 79affbe609
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 208 additions and 47 deletions

View File

@ -10,10 +10,11 @@ from pathlib import Path
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
@ -486,3 +487,30 @@ class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase):
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

View File

@ -4,11 +4,27 @@ import os
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):
@ -17,13 +33,8 @@ class ImporterTest(InvenTreeTestCase):
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()
@ -61,3 +72,15 @@ class ImporterTest(InvenTreeTestCase):
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})

View File

@ -9,7 +9,7 @@ from django.urls import reverse
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
@ -309,3 +309,11 @@ class TestLabelPrinterMachineType(TestMachineRegistryMixin, InvenTreeAPITestCase
},
expected_code=400,
)
class AdminTest(AdminTestCase):
"""Tests for the admin interface integration."""
def test_admin(self):
"""Test the admin URL."""
self.helper(model=MachineConfig)

View File

@ -16,7 +16,7 @@ from PIL import Image
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
@ -580,24 +580,6 @@ class TestReportTest(PrintTestMixins, ReportTest):
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)
@ -609,3 +591,11 @@ class TestReportTest(PrintTestMixins, ReportTest):
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)

View File

@ -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)
@ -1585,6 +1590,12 @@ class StocktakeTest(StockAPITestCase):
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)

View File

@ -79,6 +79,11 @@ class StockDetailTest(StockViewTestCase):
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."""

View File

@ -10,7 +10,7 @@ from django.test import override_settings
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
@ -1347,3 +1347,11 @@ class StockLocationTest(InvenTreeTestCase):
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)

View File

@ -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)
@ -346,7 +343,7 @@ class GetAuthToken(APIView):
return Response(data)
else:
raise exceptions.NotAuthenticated()
raise exceptions.NotAuthenticated() # pragma: no cover
class TokenListView(DestroyAPIView, ListAPI):

View File

@ -164,7 +164,7 @@ class ApiToken(AuthToken, InvenTree.models.MetadataMixin):
"""
# 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

View File

@ -64,12 +64,21 @@ class UserAPITests(InvenTreeAPITestCase):
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']}),
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."""
@ -208,3 +217,34 @@ class UserTokenTests(InvenTreeAPITestCase):
)
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)

View File

@ -5,7 +5,8 @@ from django.contrib.auth.models import Group
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
@ -270,6 +271,29 @@ class OwnerModelTest(InvenTreeTestCase):
)
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."""
@ -313,3 +337,15 @@ class MFALoginTest(InvenTreeAPITestCase):
# 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)

View File

@ -37,12 +37,22 @@ class TemplateTagTest(InvenTreeTestCase):
manifest_file = Path(__file__).parent.joinpath('static/web/.vite/manifest.json')
# Try with removed manifest file
manifest_file.rename(manifest_file.with_suffix('.json.bak')) # Rename
new_name = manifest_file.rename(
manifest_file.with_suffix('.json.bak')
) # Rename
resp = spa_helper.spa_bundle()
self.assertIsNone(resp)
manifest_file.with_suffix('.json.bak').rename(
manifest_file.with_suffix('.json')
) # Name back
# Try with differing name
resp = spa_helper.spa_bundle(new_name)
self.assertIsNotNone(resp)
# Broken manifest file
manifest_file.write_text('broken')
resp = spa_helper.spa_bundle(manifest_file)
self.assertIsNone(resp)
new_name.rename(manifest_file.with_suffix('.json')) # Name back
def test_spa_settings(self):
"""Test the 'spa_settings' template tag."""
@ -78,6 +88,11 @@ class TemplateTagTest(InvenTreeTestCase):
self.assertNotIn('show_server_selector', rsp)
self.assertEqual(rsp['server_list'], ['aa', 'bb'])
def test_redirects(self):
"""Test the redirect helper."""
response = self.client.get('/assets/testpath')
self.assertEqual(response.url, '/static/web/assets/testpath')
class TestWebHelpers(InvenTreeAPITestCase):
"""Tests for the web helpers."""