From 3c1f0637dcddfa6f6b4782e64e81883f113c0f30 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 28 Jun 2021 20:42:37 +1000 Subject: [PATCH 1/3] Adds unit tests for HTML API endpoints --- InvenTree/InvenTree/test_api.py | 87 +++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/InvenTree/InvenTree/test_api.py b/InvenTree/InvenTree/test_api.py index f196006df9..bc1157cf45 100644 --- a/InvenTree/InvenTree/test_api.py +++ b/InvenTree/InvenTree/test_api.py @@ -1,7 +1,13 @@ """ Low level tests for the InvenTree API """ +from django.http import response from rest_framework import status +from django.test import TestCase + +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group + from django.urls import reverse from InvenTree.api_tester import InvenTreeAPITestCase @@ -11,6 +17,87 @@ from users.models import RuleSet from base64 import b64encode +class HTMLAPITests(TestCase): + """ + Test that we can access the REST API endpoints via the HTML interface. + + History: Discovered on 2021-06-28 a bug in InvenTreeModelSerializer, + which raised an AssertionError when using the HTML API interface, + while the regular JSON interface continued to work as expected. + """ + + def setUp(self): + super().setUp() + + # Create a user + user = get_user_model() + + self.user = user.objects.create_user( + username='username', + email='user@email.com', + password='password' + ) + + # Put the user into a group with the correct permissions + group = Group.objects.create(name='mygroup') + self.user.groups.add(group) + + # Give the group *all* the permissions! + for rule in group.rule_sets.all(): + rule.can_view = True + rule.can_change = True + rule.can_add = True + rule.can_delete = True + + rule.save() + + self.client.login(username='username', password='password') + + def test_part_api(self): + url = reverse('api-part-list') + + # Check JSON response + response = self.client.get(url, HTTP_ACCEPT='application/json') + self.assertEqual(response.status_code, 200) + + # Check HTTP response + response = self.client.get(url, HTTP_ACCEPT='text/html') + self.assertEqual(response.status_code, 200) + + def test_build_api(self): + url = reverse('api-build-list') + + # Check JSON response + response = self.client.get(url, HTTP_ACCEPT='application/json') + self.assertEqual(response.status_code, 200) + + # Check HTTP response + response = self.client.get(url, HTTP_ACCEPT='text/html') + self.assertEqual(response.status_code, 200) + + def test_stock_api(self): + url = reverse('api-stock-list') + + # Check JSON response + response = self.client.get(url, HTTP_ACCEPT='application/json') + self.assertEqual(response.status_code, 200) + + # Check HTTP response + response = self.client.get(url, HTTP_ACCEPT='text/html') + self.assertEqual(response.status_code, 200) + + def test_company_list(self): + url = reverse('api-company-list') + + # Check JSON response + response = self.client.get(url, HTTP_ACCEPT='application/json') + self.assertEqual(response.status_code, 200) + + # Check HTTP response + response = self.client.get(url, HTTP_ACCEPT='text/html') + self.assertEqual(response.status_code, 200) + + class APITests(InvenTreeAPITestCase): """ Tests for the InvenTree API """ From 4dbd770f2d4f3bf65acef7d43cd048ef2956c482 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 28 Jun 2021 21:08:50 +1000 Subject: [PATCH 2/3] Fixed (I think?) --- InvenTree/InvenTree/serializers.py | 18 +++++++----------- InvenTree/InvenTree/test_api.py | 1 - 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index 50a37d8cba..16db21ca37 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -7,8 +7,6 @@ from __future__ import unicode_literals import os -from collections import OrderedDict - from django.conf import settings from django.contrib.auth.models import User from django.core.exceptions import ValidationError as DjangoValidationError @@ -46,16 +44,13 @@ class InvenTreeModelSerializer(serializers.ModelSerializer): def __init__(self, instance=None, data=empty, **kwargs): - self.instance = instance + # self.instance = instance # If instance is None, we are creating a new instance - if instance is None: + if instance is None and data is not empty: - if data is empty: - data = OrderedDict() - else: - # Required to side-step immutability of a QueryDict - data = data.copy() + # Required to side-step immutability of a QueryDict + data = data.copy() # Add missing fields which have default values ModelClass = self.Meta.model @@ -85,7 +80,7 @@ class InvenTreeModelSerializer(serializers.ModelSerializer): Use the 'default' values specified by the django model definition """ - initials = super().get_initial() + initials = super().get_initial().copy() # Are we creating a new instance? if self.instance is None: @@ -111,7 +106,8 @@ class InvenTreeModelSerializer(serializers.ModelSerializer): return initials def run_validation(self, data=empty): - """ Perform serializer validation. + """ + Perform serializer validation. In addition to running validators on the serializer fields, this class ensures that the underlying model is also validated. """ diff --git a/InvenTree/InvenTree/test_api.py b/InvenTree/InvenTree/test_api.py index bc1157cf45..18f9319624 100644 --- a/InvenTree/InvenTree/test_api.py +++ b/InvenTree/InvenTree/test_api.py @@ -1,6 +1,5 @@ """ Low level tests for the InvenTree API """ -from django.http import response from rest_framework import status from django.test import TestCase From f0f6c7d186f0062f4a3b3d80062dbdf00acde83c Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 28 Jun 2021 21:09:48 +1000 Subject: [PATCH 3/3] Add a comment --- InvenTree/InvenTree/serializers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index 16db21ca37..772daa06ab 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -59,6 +59,11 @@ class InvenTreeModelSerializer(serializers.ModelSerializer): for field_name, field in fields.fields.items(): + """ + Update the field IF (and ONLY IF): + - The field has a specified default value + - The field does not already have a value set + """ if field.has_default() and field_name not in data: value = field.default