2019-08-09 10:13:23 +00:00
|
|
|
""" Unit tests for Stock views (see views.py) """
|
|
|
|
|
|
|
|
from django.test import TestCase
|
|
|
|
from django.urls import reverse
|
|
|
|
from django.contrib.auth import get_user_model
|
2020-10-06 14:02:10 +00:00
|
|
|
from django.contrib.auth.models import Group
|
2019-08-09 10:13:23 +00:00
|
|
|
|
2021-01-06 10:00:45 +00:00
|
|
|
from common.models import InvenTreeSetting
|
|
|
|
|
2019-08-29 14:06:46 +00:00
|
|
|
import json
|
2021-01-04 13:01:44 +00:00
|
|
|
from datetime import datetime, timedelta
|
2019-08-29 14:06:46 +00:00
|
|
|
|
2020-12-03 16:56:45 +00:00
|
|
|
from InvenTree.status_codes import StockStatus
|
2020-12-03 12:32:01 +00:00
|
|
|
|
2019-08-09 10:13:23 +00:00
|
|
|
|
|
|
|
class StockViewTestCase(TestCase):
|
|
|
|
|
|
|
|
fixtures = [
|
|
|
|
'category',
|
|
|
|
'part',
|
|
|
|
'company',
|
|
|
|
'location',
|
|
|
|
'supplier_part',
|
|
|
|
'stock',
|
|
|
|
]
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
super().setUp()
|
|
|
|
|
|
|
|
# Create a user
|
2020-11-12 10:53:04 +00:00
|
|
|
user = get_user_model()
|
|
|
|
|
|
|
|
self.user = user.objects.create_user(
|
2020-10-06 14:02:10 +00:00
|
|
|
username='username',
|
|
|
|
email='user@email.com',
|
|
|
|
password='password'
|
|
|
|
)
|
|
|
|
|
2021-01-06 10:00:45 +00:00
|
|
|
self.user.is_staff = True
|
|
|
|
self.user.save()
|
|
|
|
|
2020-10-06 14:02:10 +00:00
|
|
|
# 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()
|
2019-08-09 10:13:23 +00:00
|
|
|
|
|
|
|
self.client.login(username='username', password='password')
|
|
|
|
|
|
|
|
|
|
|
|
class StockListTest(StockViewTestCase):
|
|
|
|
""" Tests for Stock list views """
|
|
|
|
|
|
|
|
def test_stock_index(self):
|
|
|
|
response = self.client.get(reverse('stock-index'))
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
|
|
|
|
class StockLocationTest(StockViewTestCase):
|
|
|
|
""" Tests for StockLocation views """
|
|
|
|
|
|
|
|
def test_location_edit(self):
|
|
|
|
response = self.client.get(reverse('stock-location-edit', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
def test_qr_code(self):
|
|
|
|
# Request the StockLocation QR view
|
|
|
|
response = self.client.get(reverse('stock-location-qr', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
# Test for an invalid StockLocation
|
|
|
|
response = self.client.get(reverse('stock-location-qr', args=(999,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
def test_create(self):
|
|
|
|
# Test StockLocation creation view
|
|
|
|
response = self.client.get(reverse('stock-location-create'), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
# Create with a parent
|
|
|
|
response = self.client.get(reverse('stock-location-create'), {'location': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
# Create with an invalid parent
|
|
|
|
response = self.client.get(reverse('stock-location-create'), {'location': 999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
|
|
|
|
class StockItemTest(StockViewTestCase):
|
|
|
|
"""" Tests for StockItem views """
|
|
|
|
|
|
|
|
def test_qr_code(self):
|
|
|
|
# QR code for a valid item
|
|
|
|
response = self.client.get(reverse('stock-item-qr', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
# QR code for an invalid item
|
|
|
|
response = self.client.get(reverse('stock-item-qr', args=(9999,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
def test_adjust_items(self):
|
|
|
|
url = reverse('stock-adjust')
|
|
|
|
|
|
|
|
# Move items
|
2019-08-09 10:14:47 +00:00
|
|
|
response = self.client.get(url, {'stock[]': [1, 2, 3, 4, 5], 'action': 'move'}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
2019-08-09 10:13:23 +00:00
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
# Count part
|
|
|
|
response = self.client.get(url, {'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
# Remove items
|
|
|
|
response = self.client.get(url, {'location': 1, 'action': 'take'}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
# Add items
|
|
|
|
response = self.client.get(url, {'item': 1, 'action': 'add'}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
# Blank response
|
|
|
|
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
# TODO - Tests for POST data
|
|
|
|
|
|
|
|
def test_edit_item(self):
|
|
|
|
# Test edit view for StockItem
|
|
|
|
response = self.client.get(reverse('stock-item-edit', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
# Test with a non-purchaseable part
|
|
|
|
response = self.client.get(reverse('stock-item-edit', args=(100,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
def test_create_item(self):
|
2021-01-04 13:01:44 +00:00
|
|
|
"""
|
|
|
|
Test creation of StockItem
|
|
|
|
"""
|
|
|
|
|
|
|
|
url = reverse('stock-item-create')
|
|
|
|
|
|
|
|
response = self.client.get(url, {'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
2019-08-09 10:13:23 +00:00
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
2021-01-04 13:01:44 +00:00
|
|
|
response = self.client.get(url, {'part': 999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
2019-08-09 10:13:23 +00:00
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
# Copy from a valid item, valid location
|
2021-01-04 13:01:44 +00:00
|
|
|
response = self.client.get(url, {'location': 1, 'copy': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
2019-08-09 10:13:23 +00:00
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
# Copy from an invalid item, invalid location
|
2021-01-04 13:01:44 +00:00
|
|
|
response = self.client.get(url, {'location': 999, 'copy': 9999}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
2019-08-09 10:14:47 +00:00
|
|
|
self.assertEqual(response.status_code, 200)
|
2019-08-29 14:06:46 +00:00
|
|
|
|
2021-01-04 13:01:44 +00:00
|
|
|
def test_create_stock_with_expiry(self):
|
|
|
|
"""
|
|
|
|
Test creation of stock item of a part with an expiry date.
|
|
|
|
The initial value for the "expiry_date" field should be pre-filled,
|
|
|
|
and should be in the future!
|
|
|
|
"""
|
|
|
|
|
2021-01-06 10:00:45 +00:00
|
|
|
# First, ensure that the expiry date feature is enabled!
|
|
|
|
InvenTreeSetting.set_setting('STOCK_ENABLE_EXPIRY', True, self.user)
|
|
|
|
|
2021-01-04 13:01:44 +00:00
|
|
|
url = reverse('stock-item-create')
|
|
|
|
|
|
|
|
response = self.client.get(url, {'part': 25}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
|
2019-08-09 10:14:47 +00:00
|
|
|
self.assertEqual(response.status_code, 200)
|
2019-08-29 14:06:46 +00:00
|
|
|
|
2021-01-04 13:01:44 +00:00
|
|
|
# We are expecting 10 days in the future
|
|
|
|
expiry = datetime.now().date() + timedelta(10)
|
|
|
|
|
|
|
|
expected = f'name=\\\\"expiry_date\\\\" value=\\\\"{expiry.isoformat()}\\\\"'
|
|
|
|
|
|
|
|
self.assertIn(expected, str(response.content))
|
|
|
|
|
|
|
|
# Now check with a part which does *not* have a default expiry period
|
|
|
|
response = self.client.get(url, {'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
|
|
|
|
expected = 'name=\\\\"expiry_date\\\\" placeholder=\\\\"\\\\"'
|
|
|
|
|
|
|
|
self.assertIn(expected, str(response.content))
|
|
|
|
|
2019-08-29 14:06:46 +00:00
|
|
|
def test_serialize_item(self):
|
|
|
|
# Test the serialization view
|
|
|
|
|
|
|
|
url = reverse('stock-item-serialize', args=(100,))
|
|
|
|
|
|
|
|
# GET the form
|
|
|
|
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
data_valid = {
|
|
|
|
'quantity': 5,
|
|
|
|
'serial_numbers': '1-5',
|
|
|
|
'destination': 4,
|
|
|
|
'notes': 'Serializing stock test'
|
|
|
|
}
|
|
|
|
|
|
|
|
data_invalid = {
|
|
|
|
'quantity': 4,
|
|
|
|
'serial_numbers': 'dd-23-adf',
|
|
|
|
'destination': 'blorg'
|
|
|
|
}
|
|
|
|
|
|
|
|
# POST
|
|
|
|
response = self.client.post(url, data_valid, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
data = json.loads(response.content)
|
|
|
|
self.assertTrue(data['form_valid'])
|
|
|
|
|
2019-08-29 14:14:54 +00:00
|
|
|
# Try again to serialize with the same numbers
|
|
|
|
response = self.client.post(url, data_valid, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
data = json.loads(response.content)
|
|
|
|
self.assertFalse(data['form_valid'])
|
|
|
|
|
|
|
|
# POST with invalid data
|
2019-08-29 14:06:46 +00:00
|
|
|
response = self.client.post(url, data_invalid, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
data = json.loads(response.content)
|
|
|
|
self.assertFalse(data['form_valid'])
|
2020-12-03 12:32:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
class StockOwnershipTest(StockViewTestCase):
|
|
|
|
""" Tests for stock ownership views """
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
""" Add another user for ownership tests """
|
|
|
|
|
|
|
|
super().setUp()
|
|
|
|
|
2020-12-03 16:56:45 +00:00
|
|
|
# Promote existing user with staff, admin and superuser statuses
|
2020-12-03 12:32:01 +00:00
|
|
|
self.user.is_staff = True
|
2020-12-03 16:56:45 +00:00
|
|
|
self.user.is_admin = True
|
|
|
|
self.user.is_superuser = True
|
2020-12-03 12:32:01 +00:00
|
|
|
self.user.save()
|
|
|
|
|
|
|
|
# Create a new user
|
|
|
|
user = get_user_model()
|
|
|
|
|
|
|
|
self.new_user = user.objects.create_user(
|
|
|
|
username='john',
|
|
|
|
email='john@email.com',
|
|
|
|
password='custom123',
|
|
|
|
)
|
|
|
|
|
|
|
|
# Put the user into a new group with the correct permissions
|
|
|
|
group = Group.objects.create(name='new_group')
|
|
|
|
self.new_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()
|
|
|
|
|
|
|
|
def enable_ownership(self):
|
|
|
|
# Enable stock location ownership
|
|
|
|
|
|
|
|
InvenTreeSetting.set_setting('STOCK_OWNERSHIP_CONTROL', True, self.user)
|
|
|
|
self.assertEqual(True, InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL'))
|
|
|
|
|
2020-12-03 16:56:45 +00:00
|
|
|
def test_owner_control(self):
|
|
|
|
# Test stock location and item ownership
|
|
|
|
from .models import StockLocation, StockItem
|
2021-01-12 20:02:44 +00:00
|
|
|
from users.models import Owner
|
2020-12-03 16:56:45 +00:00
|
|
|
|
|
|
|
user_group = self.user.groups.all()[0]
|
2021-01-12 20:02:44 +00:00
|
|
|
user_group_owner = Owner.get_owner(user_group)
|
2020-12-03 16:56:45 +00:00
|
|
|
new_user_group = self.new_user.groups.all()[0]
|
2021-01-12 20:02:44 +00:00
|
|
|
new_user_group_owner = Owner.get_owner(new_user_group)
|
|
|
|
|
|
|
|
user_as_owner = Owner.get_owner(self.user)
|
|
|
|
new_user_as_owner = Owner.get_owner(self.new_user)
|
2020-12-03 16:56:45 +00:00
|
|
|
|
|
|
|
test_location_id = 4
|
|
|
|
test_item_id = 11
|
2020-12-03 12:32:01 +00:00
|
|
|
|
|
|
|
# Enable ownership control
|
|
|
|
self.enable_ownership()
|
2020-12-03 16:56:45 +00:00
|
|
|
|
|
|
|
# Set ownership on existing location
|
|
|
|
response = self.client.post(reverse('stock-location-edit', args=(test_location_id,)),
|
2021-01-12 20:02:44 +00:00
|
|
|
{'name': 'Office', 'owner': user_group_owner.pk},
|
2020-12-03 12:32:01 +00:00
|
|
|
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertContains(response, '"form_valid": true', status_code=200)
|
|
|
|
|
2020-12-03 18:29:59 +00:00
|
|
|
# Set ownership on existing item (and change location)
|
2020-12-03 16:56:45 +00:00
|
|
|
response = self.client.post(reverse('stock-item-edit', args=(test_item_id,)),
|
2021-01-12 20:02:44 +00:00
|
|
|
{'part': 1, 'status': StockStatus.OK, 'owner': user_as_owner.pk},
|
2020-12-03 16:56:45 +00:00
|
|
|
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertContains(response, '"form_valid": true', status_code=200)
|
|
|
|
|
|
|
|
# Logout
|
|
|
|
self.client.logout()
|
2020-12-03 12:32:01 +00:00
|
|
|
|
|
|
|
# Login with new user
|
|
|
|
self.client.login(username='john', password='custom123')
|
|
|
|
|
|
|
|
# Test location edit
|
2020-12-03 16:56:45 +00:00
|
|
|
response = self.client.post(reverse('stock-location-edit', args=(test_location_id,)),
|
2021-01-12 20:02:44 +00:00
|
|
|
{'name': 'Office', 'owner': new_user_group_owner.pk},
|
2020-12-03 12:32:01 +00:00
|
|
|
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
2020-12-03 16:56:45 +00:00
|
|
|
|
|
|
|
# Make sure the location's owner is unchanged
|
|
|
|
location = StockLocation.objects.get(pk=test_location_id)
|
2021-01-12 20:02:44 +00:00
|
|
|
self.assertEqual(location.owner, user_group_owner)
|
2020-12-03 12:32:01 +00:00
|
|
|
|
|
|
|
# Test item edit
|
2021-01-12 21:36:29 +00:00
|
|
|
response = self.client.post(reverse('stock-item-edit', args=(test_item_id,)),
|
|
|
|
{'part': 1, 'status': StockStatus.OK, 'owner': new_user_as_owner.pk},
|
|
|
|
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
2020-12-03 16:56:45 +00:00
|
|
|
|
|
|
|
# Make sure the item's owner is unchanged
|
|
|
|
item = StockItem.objects.get(pk=test_item_id)
|
2021-01-12 20:02:44 +00:00
|
|
|
self.assertEqual(item.owner, user_as_owner)
|
2020-12-03 18:29:59 +00:00
|
|
|
|
|
|
|
# Create new parent location
|
|
|
|
parent_location = {
|
|
|
|
'name': 'John Desk',
|
|
|
|
'description': 'John\'s desk',
|
2021-01-12 20:02:44 +00:00
|
|
|
'owner': new_user_group_owner.pk,
|
2020-12-03 18:29:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# Create new parent location
|
|
|
|
response = self.client.post(reverse('stock-location-create'),
|
|
|
|
parent_location, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertContains(response, '"form_valid": true', status_code=200)
|
|
|
|
|
|
|
|
# Retrieve created location
|
|
|
|
parent_location = StockLocation.objects.get(name=parent_location['name'])
|
|
|
|
|
|
|
|
# Create new child location
|
|
|
|
new_location = {
|
|
|
|
'name': 'Upper Left Drawer',
|
|
|
|
'description': 'John\'s desk - Upper left drawer',
|
|
|
|
}
|
|
|
|
|
|
|
|
# Try to create new location with neither parent or owner
|
|
|
|
response = self.client.post(reverse('stock-location-create'),
|
|
|
|
new_location, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertContains(response, '"form_valid": false', status_code=200)
|
|
|
|
|
|
|
|
# Try to create new location with invalid owner
|
|
|
|
new_location['parent'] = parent_location.id
|
2021-01-12 20:02:44 +00:00
|
|
|
new_location['owner'] = user_group_owner.pk
|
2020-12-03 18:29:59 +00:00
|
|
|
response = self.client.post(reverse('stock-location-create'),
|
|
|
|
new_location, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertContains(response, '"form_valid": false', status_code=200)
|
|
|
|
|
|
|
|
# Try to create new location with valid owner
|
2021-01-12 20:02:44 +00:00
|
|
|
new_location['owner'] = new_user_group_owner.pk
|
2020-12-03 18:29:59 +00:00
|
|
|
response = self.client.post(reverse('stock-location-create'),
|
|
|
|
new_location, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertContains(response, '"form_valid": true', status_code=200)
|
|
|
|
|
|
|
|
# Retrieve created location
|
|
|
|
location_created = StockLocation.objects.get(name=new_location['name'])
|
|
|
|
|
|
|
|
# Create new item
|
|
|
|
new_item = {
|
|
|
|
'part': 25,
|
|
|
|
'location': location_created.pk,
|
|
|
|
'quantity': 123,
|
|
|
|
'status': StockStatus.OK,
|
|
|
|
}
|
|
|
|
|
|
|
|
# Try to create new item with no owner
|
2021-01-12 21:36:29 +00:00
|
|
|
response = self.client.post(reverse('stock-item-create'),
|
|
|
|
new_item, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertContains(response, '"form_valid": false', status_code=200)
|
2020-12-03 18:29:59 +00:00
|
|
|
|
|
|
|
# Try to create new item with invalid owner
|
2021-01-12 21:36:29 +00:00
|
|
|
new_item['owner'] = user_as_owner.pk
|
|
|
|
response = self.client.post(reverse('stock-item-create'),
|
|
|
|
new_item, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertContains(response, '"form_valid": false', status_code=200)
|
2020-12-03 18:29:59 +00:00
|
|
|
|
|
|
|
# Try to create new item with valid owner
|
2021-01-12 21:36:29 +00:00
|
|
|
new_item['owner'] = new_user_as_owner.pk
|
2020-12-03 18:29:59 +00:00
|
|
|
response = self.client.post(reverse('stock-item-create'),
|
|
|
|
new_item, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertContains(response, '"form_valid": true', status_code=200)
|
2021-01-12 22:11:46 +00:00
|
|
|
|
|
|
|
# Logout
|
|
|
|
self.client.logout()
|
|
|
|
|
|
|
|
# Login with admin
|
|
|
|
self.client.login(username='username', password='password')
|
|
|
|
|
|
|
|
# Switch owner of location
|
|
|
|
response = self.client.post(reverse('stock-location-edit', args=(location_created.pk,)),
|
|
|
|
{'name': new_location['name'], 'owner': user_group_owner.pk},
|
|
|
|
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
|
|
self.assertContains(response, '"form_valid": true', status_code=200)
|
|
|
|
|
|
|
|
# Check that owner was updated for item in this location
|
|
|
|
stock_item = StockItem.objects.all().last()
|
|
|
|
self.assertEqual(stock_item.owner, user_group_owner)
|