diff --git a/InvenTree/InvenTree/unit_test.py b/InvenTree/InvenTree/unit_test.py index bbed1bdcda..38713e0fb0 100644 --- a/InvenTree/InvenTree/unit_test.py +++ b/InvenTree/InvenTree/unit_test.py @@ -287,60 +287,60 @@ class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase): return actions - def get(self, url, data=None, expected_code=200, format='json'): + def get(self, url, data=None, expected_code=200, format='json', **kwargs): """Issue a GET request.""" # Set default - see B006 if data is None: data = {} - response = self.client.get(url, data, format=format) + response = self.client.get(url, data, format=format, **kwargs) self.checkResponse(url, 'GET', expected_code, response) return response - def post(self, url, data=None, expected_code=None, format='json'): + def post(self, url, data=None, expected_code=None, format='json', **kwargs): """Issue a POST request.""" # Set default value - see B006 if data is None: data = {} - response = self.client.post(url, data=data, format=format) + response = self.client.post(url, data=data, format=format, **kwargs) self.checkResponse(url, 'POST', expected_code, response) return response - def delete(self, url, data=None, expected_code=None, format='json'): + def delete(self, url, data=None, expected_code=None, format='json', **kwargs): """Issue a DELETE request.""" if data is None: data = {} - response = self.client.delete(url, data=data, format=format) + response = self.client.delete(url, data=data, format=format, **kwargs) self.checkResponse(url, 'DELETE', expected_code, response) return response - def patch(self, url, data, expected_code=None, format='json'): + def patch(self, url, data, expected_code=None, format='json', **kwargs): """Issue a PATCH request.""" - response = self.client.patch(url, data=data, format=format) + response = self.client.patch(url, data=data, format=format, **kwargs) self.checkResponse(url, 'PATCH', expected_code, response) return response - def put(self, url, data, expected_code=None, format='json'): + def put(self, url, data, expected_code=None, format='json', **kwargs): """Issue a PUT request.""" - response = self.client.put(url, data=data, format=format) + response = self.client.put(url, data=data, format=format, **kwargs) self.checkResponse(url, 'PUT', expected_code, response) return response - def options(self, url, expected_code=None): + def options(self, url, expected_code=None, **kwargs): """Issue an OPTIONS request.""" - response = self.client.options(url, format='json') + response = self.client.options(url, format='json', **kwargs) self.checkResponse(url, 'OPTIONS', expected_code, response) diff --git a/InvenTree/label/test_api.py b/InvenTree/label/test_api.py index 755f0cc3cd..cbfaf0b39b 100644 --- a/InvenTree/label/test_api.py +++ b/InvenTree/label/test_api.py @@ -1,43 +1,306 @@ """Unit tests for label API.""" +import json +from io import StringIO + +from django.core.cache import cache from django.urls import reverse +import label.models as label_models +from build.models import BuildLine from InvenTree.unit_test import InvenTreeAPITestCase +from part.models import Part +from stock.models import StockItem, StockLocation -class TestReportTests(InvenTreeAPITestCase): - """Tests for the StockItem TestReport templates.""" +class LabelTest(InvenTreeAPITestCase): + """Base class for unit testing label model API endpoints.""" - fixtures = ['category', 'part', 'location', 'stock'] + fixtures = ['category', 'part', 'location', 'stock', 'bom', 'build'] - roles = ['stock.view', 'stock_location.view'] + superuser = True - list_url = reverse('api-stockitem-testreport-list') + model = None + list_url = None + detail_url = None + metadata_url = None - def do_list(self, filters=None): - """Helper function to request list of labels with provided filters.""" - # Set default - see B006 - if filters is None: - filters = {} + print_url = None + print_itemname = None + print_itemmodel = None - response = self.client.get(self.list_url, filters, format='json') + def setUp(self): + """Ensure cache is cleared as part of test setup.""" + cache.clear() + return super().setUp() + def test_api_url(self): + """Test returned API Url against URL tag defined in this file.""" + if not self.list_url: + return + + self.assertEqual(reverse(self.list_url), self.model.get_api_url()) + + def test_list_endpoint(self): + """Test that the LIST endpoint works for each model.""" + if not self.list_url: + return + + url = reverse(self.list_url) + + response = self.get(url) self.assertEqual(response.status_code, 200) - return response.data + labels = self.model.objects.all() + n = len(labels) - def test_list(self): - """Test the API list endpoint.""" - response = self.do_list() + # API endpoint must return correct number of reports + self.assertEqual(len(response.data), n) - # TODO - Add some report templates to the fixtures - self.assertEqual(len(response), 0) + # Filter by "enabled" status + response = self.get(url, {'enabled': True}) + self.assertEqual(len(response.data), n) - # TODO - Add some tests to this response - response = self.do_list({'item': 10}) + response = self.get(url, {'enabled': False}) + self.assertEqual(len(response.data), 0) - # TODO - Add some tests to this response - response = self.do_list({'item': 100000}) + # Disable each report + for label in labels: + label.enabled = False + label.save() - # TODO - Add some tests to this response - response = self.do_list({'items': [10, 11, 12]}) + # Filter by "enabled" status + response = self.get(url, {'enabled': True}) + self.assertEqual(len(response.data), 0) + + response = self.get(url, {'enabled': False}) + self.assertEqual(len(response.data), n) + + def test_create_endpoint(self): + """Test that creating a new report works for each label.""" + if not self.list_url: + return + + url = reverse(self.list_url) + + # Create a new label + # Django REST API "APITestCase" does not work like requests - to send a file without it existing on disk, + # create it as a StringIO object, and upload it under parameter template + filestr = StringIO( + '{% extends "label/label_base.html" %}{% block content %}
TEST LABEL{% endblock content %}' + ) + filestr.name = 'ExampleTemplate.html' + + response = self.post( + url, + data={ + 'name': 'New label', + 'description': 'A fancy new label created through API test', + 'label': filestr, + }, + format=None, + expected_code=201, + ) + + # Make sure the expected keys are in the response + self.assertIn('pk', response.data) + self.assertIn('name', response.data) + self.assertIn('description', response.data) + self.assertIn('label', response.data) + self.assertIn('filters', response.data) + self.assertIn('enabled', response.data) + + self.assertEqual(response.data['name'], 'New label') + self.assertEqual( + response.data['description'], 'A fancy new label created through API test' + ) + self.assertEqual(response.data['label'].count('ExampleTemplate'), 1) + + def test_detail_endpoint(self): + """Test that the DETAIL endpoint works for each label.""" + if not self.detail_url: + return + + # Create an item first + self.test_create_endpoint() + + labels = self.model.objects.all() + + n = len(labels) + + # Make sure at least one report defined + self.assertGreaterEqual(n, 1) + + # Check detail page for first report + response = self.get( + reverse(self.detail_url, kwargs={'pk': labels[0].pk}), expected_code=200 + ) + + # Make sure the expected keys are in the response + self.assertIn('pk', response.data) + self.assertIn('name', response.data) + self.assertIn('description', response.data) + self.assertIn('label', response.data) + self.assertIn('filters', response.data) + self.assertIn('enabled', response.data) + + filestr = StringIO( + '{% extends "label/label_base.html" %}{% block content %}
TEST LABEL{% endblock content %}' + ) + filestr.name = 'ExampleTemplate_Updated.html' + + # Check PATCH method + response = self.patch( + reverse(self.detail_url, kwargs={'pk': labels[0].pk}), + { + 'name': 'Changed name during test', + 'description': 'New version of the template', + 'label': filestr, + }, + format=None, + expected_code=200, + ) + + # Make sure the expected keys are in the response + self.assertIn('pk', response.data) + self.assertIn('name', response.data) + self.assertIn('description', response.data) + self.assertIn('label', response.data) + self.assertIn('filters', response.data) + self.assertIn('enabled', response.data) + + self.assertEqual(response.data['name'], 'Changed name during test') + self.assertEqual(response.data['description'], 'New version of the template') + + self.assertEqual(response.data['label'].count('ExampleTemplate_Updated'), 1) + + def test_delete(self): + """Test deleting, after other test are done.""" + if not self.detail_url: + return + + # Create an item first + self.test_create_endpoint() + + labels = self.model.objects.all() + n = len(labels) + # Make sure at least one label defined + self.assertGreaterEqual(n, 1) + + # Delete the last report + response = self.delete( + reverse(self.detail_url, kwargs={'pk': labels[n - 1].pk}), expected_code=204 + ) + + def test_print_label(self): + """Test printing a label.""" + if not self.print_url: + return + + # Create an item first + self.test_create_endpoint() + + labels = self.model.objects.all() + n = len(labels) + # Make sure at least one label defined + self.assertGreaterEqual(n, 1) + + url = reverse(self.print_url, kwargs={'pk': labels[0].pk}) + + # Try to print without providing a valid item + response = self.get(url, expected_code=400) + + # Try to print with an invalid item + response = self.get(url, {self.print_itemname: 9999}, expected_code=400) + + # Now print with a valid item + print(f'{self.print_itemmodel = }') + print(f'{self.print_itemmodel.objects.all() = }') + + item = self.print_itemmodel.objects.first() + self.assertIsNotNone(item) + + response = self.get(url, {self.print_itemname: item.pk}, expected_code=200) + + response_json = json.loads(response.content.decode('utf-8')) + + self.assertIn('file', response_json) + self.assertIn('success', response_json) + self.assertIn('message', response_json) + self.assertTrue(response_json['success']) + + def test_metadata_endpoint(self): + """Unit tests for the metadata field.""" + if not self.metadata_url: + return + + # Create an item first + self.test_create_endpoint() + + labels = self.model.objects.all() + n = len(labels) + # Make sure at least one label defined + self.assertGreaterEqual(n, 1) + + # Test getting metadata + response = self.get( + reverse(self.metadata_url, kwargs={'pk': labels[0].pk}), expected_code=200 + ) + + self.assertEqual(response.data, {'metadata': {}}) + + +class TestStockItemLabel(LabelTest): + """Unit testing class for the StockItemLabel model.""" + + model = label_models.StockItemLabel + + list_url = 'api-stockitem-label-list' + detail_url = 'api-stockitem-label-detail' + metadata_url = 'api-stockitem-label-metadata' + + print_url = 'api-stockitem-label-print' + print_itemname = 'item' + print_itemmodel = StockItem + + +class TestStockLocationLabel(LabelTest): + """Unit testing class for the StockLocationLabel model.""" + + model = label_models.StockLocationLabel + + list_url = 'api-stocklocation-label-list' + detail_url = 'api-stocklocation-label-detail' + metadata_url = 'api-stocklocation-label-metadata' + + print_url = 'api-stocklocation-label-print' + print_itemname = 'location' + print_itemmodel = StockLocation + + +class TestPartLabel(LabelTest): + """Unit testing class for the PartLabel model.""" + + model = label_models.PartLabel + + list_url = 'api-part-label-list' + detail_url = 'api-part-label-detail' + metadata_url = 'api-part-label-metadata' + + print_url = 'api-part-label-print' + print_itemname = 'part' + print_itemmodel = Part + + +class TestBuildLineLabel(LabelTest): + """Unit testing class for the BuildLine model.""" + + model = label_models.BuildLineLabel + + list_url = 'api-buildline-label-list' + detail_url = 'api-buildline-label-detail' + metadata_url = 'api-buildline-label-metadata' + + print_url = 'api-buildline-label-print' + print_itemname = 'line' + print_itemmodel = BuildLine diff --git a/InvenTree/report/tests.py b/InvenTree/report/tests.py index be6dd65c6e..8f43aca98d 100644 --- a/InvenTree/report/tests.py +++ b/InvenTree/report/tests.py @@ -2,6 +2,7 @@ import os import shutil +from io import StringIO from pathlib import Path from django.conf import settings @@ -200,6 +201,8 @@ class ReportTest(InvenTreeAPITestCase): 'build', ] + superuser = True + model = None list_url = None detail_url = None @@ -238,6 +241,13 @@ class ReportTest(InvenTreeAPITestCase): enabled=True, ) + def test_api_url(self): + """Test returned API Url against URL tag defined in this file.""" + if not self.list_url: + return + + self.assertEqual(reverse(self.list_url), self.model.get_api_url()) + def test_list_endpoint(self): """Test that the LIST endpoint works for each report.""" if not self.list_url: @@ -273,6 +283,109 @@ class ReportTest(InvenTreeAPITestCase): response = self.get(url, {'enabled': False}) self.assertEqual(len(response.data), n) + def test_create_endpoint(self): + """Test that creating a new report works for each report.""" + if not self.list_url: + return + + url = reverse(self.list_url) + + # Create a new report + # Django REST API "APITestCase" does not work like requests - to send a file without it existing on disk, + # create it as a StringIO object, and upload it under parameter template + filestr = StringIO( + '{% extends "label/report_base.html" %}{% block content %}
TEST REPORT{% endblock content %}' + ) + filestr.name = 'ExampleTemplate.html' + + response = self.post( + url, + data={ + 'name': 'New report', + 'description': 'A fancy new report created through API test', + 'template': filestr, + }, + format=None, + expected_code=201, + ) + + # Make sure the expected keys are in the response + self.assertIn('pk', response.data) + self.assertIn('name', response.data) + self.assertIn('description', response.data) + self.assertIn('template', response.data) + self.assertIn('filters', response.data) + self.assertIn('enabled', response.data) + + self.assertEqual(response.data['name'], 'New report') + self.assertEqual( + response.data['description'], 'A fancy new report created through API test' + ) + self.assertTrue(response.data['template'].endswith('ExampleTemplate.html')) + + def test_detail_endpoint(self): + """Test that the DETAIL endpoint works for each report.""" + if not self.detail_url: + return + + reports = self.model.objects.all() + + n = len(reports) + + # Make sure at least one report defined + self.assertGreaterEqual(n, 1) + + # Check detail page for first report + response = self.get( + reverse(self.detail_url, kwargs={'pk': reports[0].pk}), expected_code=200 + ) + + # Make sure the expected keys are in the response + self.assertIn('pk', response.data) + self.assertIn('name', response.data) + self.assertIn('description', response.data) + self.assertIn('template', response.data) + self.assertIn('filters', response.data) + self.assertIn('enabled', response.data) + + filestr = StringIO( + '{% extends "label/report_base.html" %}{% block content %}
TEST REPORT VERSION 2{% endblock content %}' + ) + filestr.name = 'ExampleTemplate_Updated.html' + + # Check PATCH method + response = self.patch( + reverse(self.detail_url, kwargs={'pk': reports[0].pk}), + { + 'name': 'Changed name during test', + 'description': 'New version of the template', + 'template': filestr, + }, + format=None, + expected_code=200, + ) + + # Make sure the expected keys are in the response + self.assertIn('pk', response.data) + self.assertIn('name', response.data) + self.assertIn('description', response.data) + self.assertIn('template', response.data) + self.assertIn('filters', response.data) + self.assertIn('enabled', response.data) + + self.assertEqual(response.data['name'], 'Changed name during test') + self.assertEqual(response.data['description'], 'New version of the template') + + self.assertTrue( + response.data['template'].endswith('ExampleTemplate_Updated.html') + ) + + # Delete the last report + response = self.delete( + reverse(self.detail_url, kwargs={'pk': reports[n - 1].pk}), + expected_code=204, + ) + def test_metadata(self): """Unit tests for the metadata field.""" if self.model is not None: