toolshed/backend/toolshed/tests/test_inventory.py
2026-08-27 01:36:29 +02:00

688 lines
35 KiB
Python

from authentication.models import Group
from authentication.tests import SignatureAuthClient, UserTestMixin, GroupTestMixin, ToolshedTestCase
from files.tests import FilesTestMixin
from toolshed.models import InventoryItem, Category, StorageLocation
from toolshed.tests import InventoryTestMixin, CategoryTestMixin, TagTestMixin, PropertyTestMixin, LocationTestMixin
client = SignatureAuthClient()
class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
def setUp(self):
super().setUp()
self.prepare_users()
self.prepare_categories()
self.prepare_tags()
self.prepare_properties()
self.prepare_inventory()
self.own_handle = str(self.f['local_user1'])
def test_get_inventory(self):
reply = client.get('/api/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(len(reply.json()), 2)
self.assertEqual(reply.json()[0]['name'], 'test1')
self.assertEqual(reply.json()[0]['description'], 'test')
self.assertEqual(reply.json()[0]['owned_quantity'], 1)
self.assertEqual(reply.json()[0]['tags'], [])
self.assertEqual(reply.json()[0]['properties'], [])
self.assertEqual(reply.json()[0]['category'], 'cat1')
self.assertEqual(reply.json()[0]['availability_policy'], 'friends')
self.assertEqual(reply.json()[1]['name'], 'test2')
self.assertEqual(reply.json()[1]['description'], 'test2')
self.assertEqual(reply.json()[1]['owned_quantity'], 1)
self.assertEqual(reply.json()[1]['tags'], ['tag1', 'tag2'])
self.assertEqual(reply.json()[1]['properties'],
[{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}])
self.assertEqual(reply.json()[1]['category'], 'cat1')
self.assertEqual(reply.json()[1]['availability_policy'], 'friends')
def test_post_new_item(self):
reply = client.post('/api/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'], {
'availability_policy': 'rent',
'category': 'cat2',
'name': 'test3',
'description': 'test',
'owned_quantity': 1,
'image': '',
'tags': ['tag1', 'tag2'],
'properties': [{'name': 'prop1', 'value': 'value3'}, {'name': 'prop2', 'value': 'value4'}]
})
self.assertEqual(reply.status_code, 201)
self.assertEqual(InventoryItem.objects.count(), 3)
item = InventoryItem.objects.get(name='test3')
self.assertEqual(item.availability_policy, 'rent')
self.assertEqual(item.category, Category.objects.get(name='cat2'))
self.assertEqual(item.name, 'test3')
self.assertEqual(item.description, 'test')
self.assertEqual(item.owned_quantity, 1)
self.assertEqual([t for t in item.tags.all()], [self.f['tag1'], self.f['tag2']])
self.assertEqual([p for p in item.properties.all()], [self.f['prop1'], self.f['prop2']])
self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value3', 'value4'])
def test_post_new_item2(self):
reply = client.post('/api/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'], {
'availability_policy': 'share',
'name': 'test3',
'description': 'test',
'owned_quantity': 1,
'image': '',
})
self.assertEqual(reply.status_code, 201)
self.assertEqual(InventoryItem.objects.count(), 3)
item = InventoryItem.objects.get(name='test3')
self.assertEqual(item.availability_policy, 'share')
self.assertEqual(item.category, None)
self.assertEqual(item.name, 'test3')
self.assertEqual(item.description, 'test')
self.assertEqual(item.owned_quantity, 1)
self.assertEqual([t for t in item.tags.all()], [])
self.assertEqual([p for p in item.properties.all()], [])
def test_post_new_item_empty(self):
reply = client.post('/api/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'], {
'availability_policy': 'rent',
'owned_quantity': 1,
'image': '',
})
self.assertEqual(reply.status_code, 400)
self.assertEqual(InventoryItem.objects.count(), 2)
def test_post_new_item3(self):
reply = client.post('/api/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'], {
'availability_policy': 'private',
'name': 'test3',
'description': 'test',
'owned_quantity': 1,
'image': '',
'category': None,
})
self.assertEqual(reply.status_code, 201)
self.assertEqual(InventoryItem.objects.count(), 3)
item = InventoryItem.objects.get(name='test3')
self.assertEqual(item.availability_policy, 'private')
self.assertEqual(item.category, None)
self.assertEqual(item.name, 'test3')
self.assertEqual(item.description, 'test')
self.assertEqual(item.owned_quantity, 1)
self.assertEqual([t for t in item.tags.all()], [])
self.assertEqual([p for p in item.properties.all()], [])
def test_put_item(self):
reply = client.put('/api/inventory_items/{}/1/'.format(self.own_handle), self.f['local_user1'], {
'availability_policy': 'sell',
'name': 'test4',
'description': 'new description',
'owned_quantity': 100,
'image': '',
'tags': ['tag1', 'tag2', 'tag3'],
'properties': [{'name': 'prop1', 'value': 'value5'}, {'name': 'prop2', 'value': 'value6'},
{'name': 'prop3', 'value': 'value7'}]
})
self.assertEqual(reply.status_code, 200)
self.assertEqual(InventoryItem.objects.count(), 2)
item = InventoryItem.objects.get(id=1)
self.assertEqual(item.availability_policy, 'sell')
self.assertEqual(item.category, None)
self.assertEqual(item.name, 'test4')
self.assertEqual(item.description, 'new description')
self.assertEqual(item.owned_quantity, 100)
self.assertEqual([t for t in item.tags.all()], [self.f['tag1'], self.f['tag2'], self.f['tag3']])
self.assertEqual([p for p in item.properties.all()], [self.f['prop1'], self.f['prop2'], self.f['prop3']])
self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value5', 'value6', 'value7'])
def test_patch_item(self):
reply = client.patch('/api/inventory_items/{}/1/'.format(self.own_handle), self.f['local_user1'], {
'description': 'new description2',
'category': 'cat1',
'owned_quantity': 100,
'tags': ['tag3'],
'properties': [{'name': 'prop3', 'value': 'value8'}]
})
self.assertEqual(reply.status_code, 200)
self.assertEqual(InventoryItem.objects.count(), 2)
item = InventoryItem.objects.get(id=1)
self.assertEqual(item.availability_policy, 'friends')
self.assertEqual(item.category, Category.objects.get(name='cat1'))
self.assertEqual(item.name, 'test1')
self.assertEqual(item.description, 'new description2')
self.assertEqual(item.owned_quantity, 100)
self.assertEqual([t for t in item.tags.all()], [self.f['tag3']])
self.assertEqual([p for p in item.properties.all()], [self.f['prop3']])
self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value8'])
def test_patch_item2(self):
reply = client.patch('/api/inventory_items/{}/1/'.format(self.own_handle), self.f['local_user1'], {
'description': 'new description2',
'category': None,
'owned_quantity': 100,
'tags': [],
'properties': []
})
self.assertEqual(reply.status_code, 200)
self.assertEqual(InventoryItem.objects.count(), 2)
item = InventoryItem.objects.get(id=1)
self.assertEqual(item.availability_policy, 'friends')
self.assertEqual(item.category, None)
self.assertEqual(item.name, 'test1')
self.assertEqual(item.description, 'new description2')
self.assertEqual(item.owned_quantity, 100)
self.assertEqual([t for t in item.tags.all()], [])
self.assertEqual([p for p in item.properties.all()], [])
def test_delete_item(self):
reply = client.delete('/api/inventory_items/{}/1/'.format(self.own_handle), self.f['local_user1'])
self.assertEqual(reply.status_code, 204)
self.assertEqual(InventoryItem.objects.count(), 1)
self.assertEqual(InventoryItem.objects.get(id=2).name, 'test2')
self.assertEqual(InventoryItem.objects.filter(name='test1').count(), 0)
def test_search_items(self):
reply = client.get('/api/search/?query=test', self.f['local_user1'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(len(reply.json()), 2)
self.assertEqual(reply.json()[0]['name'], 'test1')
self.assertEqual(reply.json()[0]['description'], 'test')
self.assertEqual(reply.json()[0]['owned_quantity'], 1)
self.assertEqual(reply.json()[0]['tags'], [])
self.assertEqual(reply.json()[0]['properties'], [])
self.assertEqual(reply.json()[0]['category'], 'cat1')
self.assertEqual(reply.json()[0]['availability_policy'], 'friends')
self.assertEqual(reply.json()[1]['name'], 'test2')
self.assertEqual(reply.json()[1]['description'], 'test2')
self.assertEqual(reply.json()[1]['owned_quantity'], 1)
self.assertEqual(reply.json()[1]['tags'], ['tag1', 'tag2'])
self.assertEqual(reply.json()[1]['properties'],
[{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}])
self.assertEqual(reply.json()[1]['category'], 'cat1')
self.assertEqual(reply.json()[1]['availability_policy'], 'friends')
def test_search_items2(self):
reply = client.get('/api/search/?query=test', self.f['local_user2'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(len(reply.json()), 2)
self.assertEqual(reply.json()[0]['name'], 'test1')
self.assertEqual(reply.json()[1]['name'], 'test2')
def test_search_items_fail(self):
reply = client.get('/api/search/', self.f['local_user1'])
self.assertEqual(reply.status_code, 400)
self.assertEqual(reply.json()['error'], 'No query provided.')
def test_search_items_fail2(self):
reply = client.get('/api/search/?query=test', self.f['ext_user1'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(len(reply.json()), 0)
def test_get_shared_item_as_friend(self):
reply = client.get('/api/inventory_items/testuser1@example.com/' + str(self.f['item1'].id) + '/',
self.f['local_user2'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.json()['name'], 'test1')
self.assertEqual(reply.json()['owner'], 'testuser1@example.com')
def test_get_shared_item_as_owner(self):
reply = client.get('/api/inventory_items/testuser1@example.com/' + str(self.f['item1'].id) + '/',
self.f['local_user1'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.json()['name'], 'test1')
def test_get_shared_item_not_friend(self):
# Non-visible resolves the same way a non-member's group access does now (see
# GroupOwnedInventoryApiTestCase.test_non_member_cannot_see_or_edit) - 404, not a
# separate 403 special case for "not a friend".
reply = client.get('/api/inventory_items/testuser1@example.com/' + str(self.f['item1'].id) + '/',
self.f['ext_user1'])
self.assertEqual(reply.status_code, 404)
def test_get_shared_item_private(self):
private_item = InventoryItem.create_for_owner(
owner=self.f['local_user1'], owned_quantity=1, name='secret', availability_policy='private')
reply = client.get('/api/inventory_items/testuser1@example.com/' + str(private_item.id) + '/',
self.f['local_user2'])
self.assertEqual(reply.status_code, 404)
reply = client.get('/api/inventory_items/testuser1@example.com/' + str(private_item.id) + '/',
self.f['local_user1'])
self.assertEqual(reply.status_code, 200)
def test_get_shared_item_unknown_handle(self):
reply = client.get('/api/inventory_items/nobody@example.com/' + str(self.f['item1'].id) + '/',
self.f['local_user2'])
self.assertEqual(reply.status_code, 404)
def test_get_shared_item_unknown_id(self):
reply = client.get('/api/inventory_items/testuser1@example.com/99999/', self.f['local_user2'])
self.assertEqual(reply.status_code, 404)
def test_get_shared_item_bad_handle(self):
reply = client.get('/api/inventory_items/testuser1/' + str(self.f['item1'].id) + '/', self.f['local_user2'])
self.assertEqual(reply.status_code, 400)
class ResolveShortIdApiTestCase(UserTestMixin, InventoryTestMixin, GroupTestMixin, LocationTestMixin,
ToolshedTestCase):
def setUp(self):
super().setUp()
self.prepare_users()
self.prepare_categories()
self.prepare_tags()
self.prepare_properties()
self.prepare_inventory()
self.prepare_groups()
self.prepare_locations()
self.owner_id = self.f['local_user1'].public_identity.pk
def test_resolve_item_as_owner(self):
reply = client.get(f'/api/resolve_short_id/item/{self.owner_id}/{self.f["item1"].id}/',
self.f['local_user1'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.json(), {'handle': 'testuser1@example.com', 'id': self.f['item1'].id})
def test_resolve_item_as_friend(self):
reply = client.get(f'/api/resolve_short_id/item/{self.owner_id}/{self.f["item1"].id}/',
self.f['local_user2'])
self.assertEqual(reply.status_code, 200)
def test_resolve_item_not_friend(self):
reply = client.get(f'/api/resolve_short_id/item/{self.owner_id}/{self.f["item1"].id}/',
self.f['ext_user1'])
self.assertEqual(reply.status_code, 403)
def test_resolve_item_private_not_owner(self):
private_item = InventoryItem.create_for_owner(
owner=self.f['local_user1'], owned_quantity=1, name='secret', availability_policy='private')
reply = client.get(f'/api/resolve_short_id/item/{self.owner_id}/{private_item.id}/',
self.f['local_user2'])
self.assertEqual(reply.status_code, 403)
reply = client.get(f'/api/resolve_short_id/item/{self.owner_id}/{private_item.id}/',
self.f['local_user1'])
self.assertEqual(reply.status_code, 200)
def test_resolve_item_unknown_owner(self):
reply = client.get('/api/resolve_short_id/item/999999/1/', self.f['local_user2'])
self.assertEqual(reply.status_code, 404)
def test_resolve_item_unknown_local_id(self):
reply = client.get(f'/api/resolve_short_id/item/{self.owner_id}/999999/', self.f['local_user2'])
self.assertEqual(reply.status_code, 404)
def test_resolve_unsupported_kind(self):
# workflow/category/file/group aren't wired up yet - see
# docs/handles-and-shortids.md#domain-qualified-short-id's note on the remaining gap.
reply = client.get(f'/api/resolve_short_id/workflow/{self.owner_id}/1/', self.f['local_user2'])
self.assertEqual(reply.status_code, 400)
def test_resolve_storage_location_as_owner(self):
reply = client.get(f'/api/resolve_short_id/storage_location/{self.owner_id}/{self.f["loc1"].id}/',
self.f['local_user1'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.json(), {'handle': 'testuser1@example.com', 'id': self.f['loc1'].id})
def test_resolve_storage_location_unknown_local_id(self):
reply = client.get(f'/api/resolve_short_id/storage_location/{self.owner_id}/999999/',
self.f['local_user1'])
self.assertEqual(reply.status_code, 404)
def test_resolve_group_item_as_member(self):
item = InventoryItem.create_for_owner(
owner_group=self.f['group1'], owned_quantity=1, name='drill', availability_policy='private')
reply = client.get(f'/api/resolve_short_id/group_item/{self.f["group1"].pk}/{item.id}/',
self.f['local_user1'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.json(), {'handle': str(self.f['group1']), 'id': item.id})
def test_resolve_group_item_non_member_denied(self):
item = InventoryItem.create_for_owner(
owner_group=self.f['group1'], owned_quantity=1, name='drill', availability_policy='private')
reply = client.get(f'/api/resolve_short_id/group_item/{self.f["group1"].pk}/{item.id}/',
self.f['local_user2'])
self.assertEqual(reply.status_code, 403)
def test_resolve_group_storage_location_as_member(self):
location = StorageLocation.create_for_owner(owner_group=self.f['group1'], name='shelf')
reply = client.get(
f'/api/resolve_short_id/group_storage_location/{self.f["group1"].pk}/{location.id}/',
self.f['local_user1'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.json(), {'handle': str(self.f['group1']), 'id': location.id})
def test_resolve_unknown_group(self):
reply = client.get('/api/resolve_short_id/group_item/999999/1/', self.f['local_user1'])
self.assertEqual(reply.status_code, 404)
class TestInventoryItemWithFileApiTestCase(UserTestMixin, FilesTestMixin, InventoryTestMixin, ToolshedTestCase):
def setUp(self):
super().setUp()
self.prepare_users()
self.prepare_categories()
self.prepare_tags()
self.prepare_properties()
self.prepare_files()
self.prepare_inventory()
self.own_handle = str(self.f['local_user1'])
def test_post_item_with_file_id(self):
reply = client.post('/api/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'], {
'name': 'test4',
'description': 'test',
'category': 'cat1',
'owned_quantity': 1,
'tags': ['tag1', 'tag2'],
'properties': [{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}],
'files': [self.f['test_file1'].id]
})
self.assertEqual(reply.status_code, 201)
self.assertEqual(InventoryItem.objects.count(), 3)
item = InventoryItem.objects.get(id=3)
self.assertEqual(item.availability_policy, 'private')
self.assertEqual(item.category, Category.objects.get(name='cat1'))
self.assertEqual(item.name, 'test4')
self.assertEqual(item.description, 'test')
self.assertEqual(item.owned_quantity, 1)
self.assertEqual([t for t in item.tags.all()], [self.f['tag1'], self.f['tag2']])
self.assertEqual([p for p in item.properties.all()], [self.f['prop1'], self.f['prop2']])
self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value1', 'value2'])
self.assertEqual([f for f in item.files.all()], [self.f['test_file1']])
def test_post_item_with_encoded_file(self):
reply = client.post('/api/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'], {
'name': 'test4',
'description': 'test',
'category': 'cat1',
'owned_quantity': 1,
'tags': ['tag1', 'tag2'],
'properties': [{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}],
'files': [{'data': self.f['encoded_content3'], 'mime_type': 'text/plain'}]
})
self.assertEqual(reply.status_code, 201)
self.assertEqual(InventoryItem.objects.count(), 3)
item = InventoryItem.objects.get(id=3)
self.assertEqual(item.availability_policy, 'private')
self.assertEqual(item.category, Category.objects.get(name='cat1'))
self.assertEqual(item.name, 'test4')
self.assertEqual(item.description, 'test')
self.assertEqual(item.owned_quantity, 1)
self.assertEqual([t for t in item.tags.all()], [self.f['tag1'], self.f['tag2']])
self.assertEqual([p for p in item.properties.all()], [self.f['prop1'], self.f['prop2']])
self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value1', 'value2'])
self.assertEqual([f for f in item.files.all()], [self.f['test_file3']])
def test_post_item_with_file_id_fail(self):
reply = client.post('/api/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'], {
'name': 'test4',
'description': 'test',
'category': 'cat1',
'owned_quantity': 1,
'tags': ['tag1', 'tag2'],
'properties': [{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}],
'files': [99999]
})
self.assertEqual(reply.status_code, 400)
def test_post_item_with_encoded_file_fail(self):
reply = client.post('/api/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'], {
'name': 'test4',
'description': 'test',
'category': 'cat1',
'owned_quantity': 1,
'tags': ['tag1', 'tag2'],
'properties': [{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}],
'files': [{'data': self.f['encoded_content3']}]
})
self.assertEqual(reply.status_code, 400)
class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTestMixin, TagTestMixin,
PropertyTestMixin, FilesTestMixin, LocationTestMixin, ToolshedTestCase):
def setUp(self):
super().setUp()
self.prepare_users()
self.prepare_groups()
self.prepare_categories()
self.prepare_tags()
self.prepare_properties()
self.prepare_files()
self.prepare_locations()
self.f['group1'].members.add(self.f['local_user2'].public_identity)
self.group_handle = '+' + str(self.f['group1'])[1:]
def create_group_item(self, name='drill'):
return client.post('/api/inventory_items/{}/'.format(self.group_handle), self.f['local_user1'], {
'name': name, 'owned_quantity': 1, 'availability_policy': 'private',
})
def test_create_group_owned_item(self):
reply = self.create_group_item()
self.assertEqual(reply.status_code, 201)
item = InventoryItem.objects.get(name='drill')
self.assertIsNone(item.owner)
self.assertEqual(item.owner_group, self.f['group1'])
self.assertEqual(reply.json()['owner_group'], str(self.f['group1']))
self.assertIsNone(reply.json()['owner'])
def test_create_group_owned_item_non_member_denied(self):
reply = client.post('/api/inventory_items/{}/'.format(self.group_handle), self.f['ext_user1'], {
'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private',
})
self.assertEqual(reply.status_code, 403)
self.assertEqual(InventoryItem.objects.count(), 0)
def test_other_member_can_edit(self):
item_id = self.create_group_item().json()['id']
reply = client.patch('/api/inventory_items/{}/{}/'.format(self.group_handle, item_id), self.f['local_user2'], {
'name': 'drill-renamed'
})
self.assertEqual(reply.status_code, 200)
self.assertEqual(InventoryItem.objects.get(id=item_id).name, 'drill-renamed')
def test_other_member_can_delete(self):
item_id = self.create_group_item().json()['id']
reply = client.delete('/api/inventory_items/{}/{}/'.format(self.group_handle, item_id), self.f['local_user2'])
self.assertEqual(reply.status_code, 204)
self.assertEqual(InventoryItem.objects.filter(id=item_id).count(), 0)
def test_delete_group_item_when_id_collides_with_own_personal_item(self):
own_handle = str(self.f['local_user2'])
personal_reply = client.post('/api/inventory_items/{}/'.format(own_handle), self.f['local_user2'], {
'name': 'personal-drill', 'owned_quantity': 1, 'availability_policy': 'private',
})
self.assertEqual(personal_reply.json()['id'], 1)
item_id = self.create_group_item().json()['id']
self.assertEqual(item_id, 1)
reply = client.delete('/api/inventory_items/{}/{}/'.format(self.group_handle, item_id), self.f['local_user2'])
self.assertEqual(reply.status_code, 204)
self.assertEqual(InventoryItem.objects.filter(name='drill').count(), 0)
self.assertEqual(InventoryItem.objects.filter(name='personal-drill').count(), 1)
def test_remote_member_without_local_account_can_edit(self):
# A remote member (KnownIdentity, no ToolshedUser row) must still act on group-owned
# items - not unauthorized just because .user.exists() is False.
self.f['group1'].members.add(self.f['ext_user1'].public_identity)
item_id = self.create_group_item().json()['id']
reply = client.get('/api/inventory_items/{}/{}/'.format(self.group_handle, item_id), self.f['ext_user1'])
self.assertEqual(reply.status_code, 200)
reply = client.patch('/api/inventory_items/{}/{}/'.format(self.group_handle, item_id), self.f['ext_user1'], {
'name': 'drill-renamed-by-remote-member'
})
self.assertEqual(reply.status_code, 200)
self.assertEqual(InventoryItem.objects.get(id=item_id).name, 'drill-renamed-by-remote-member')
def test_non_member_cannot_see_or_edit(self):
item_id = self.create_group_item().json()['id']
reply = client.get('/api/inventory_items/{}/{}/'.format(self.group_handle, item_id), self.f['ext_user1'])
self.assertEqual(reply.status_code, 404)
def test_group_items_excluded_from_personal_list(self):
self.create_group_item()
reply = client.get('/api/inventory_items/{}/'.format(str(self.f['local_user1'])), self.f['local_user1'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(len(reply.json()), 0)
def test_group_items_listed_by_owner_handle(self):
self.create_group_item()
reply = client.get('/api/inventory_items/{}/'.format(self.group_handle), self.f['local_user2'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(len(reply.json()), 1)
self.assertEqual(reply.json()[0]['name'], 'drill')
def test_group_items_not_listed_for_non_member(self):
self.create_group_item()
reply = client.get('/api/inventory_items/{}/'.format(self.group_handle), self.f['ext_user1'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(len(reply.json()), 0)
def test_create_group_owned_item_with_full_fields(self):
# Parity with InventoryApiTestCase.test_post_new_item: tags/properties/category attach
# to a group-owned item the same way as a personal one.
reply = client.post('/api/inventory_items/{}/'.format(self.group_handle), self.f['local_user1'], {
'availability_policy': 'rent',
'category': 'cat2',
'name': 'drill',
'description': 'test',
'owned_quantity': 3,
'tags': ['tag1', 'tag2'],
'properties': [{'name': 'prop1', 'value': 'value1'}, {'name': 'prop2', 'value': 'value2'}],
})
self.assertEqual(reply.status_code, 201)
item = InventoryItem.objects.get(name='drill')
self.assertIsNone(item.owner)
self.assertEqual(item.owner_group, self.f['group1'])
self.assertEqual(item.availability_policy, 'rent')
self.assertEqual(item.category, Category.objects.get(name='cat2'))
self.assertEqual(item.owned_quantity, 3)
self.assertEqual([t for t in item.tags.all()], [self.f['tag1'], self.f['tag2']])
self.assertEqual([p for p in item.properties.all()], [self.f['prop1'], self.f['prop2']])
self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value1', 'value2'])
def test_create_group_owned_item_empty_fails(self):
# Parity with InventoryApiTestCase.test_post_new_item_empty: clean()'s name-or-files validation still applies.
reply = client.post('/api/inventory_items/{}/'.format(self.group_handle), self.f['local_user1'], {
'availability_policy': 'private', 'owned_quantity': 1,
})
self.assertEqual(reply.status_code, 400)
self.assertEqual(InventoryItem.objects.count(), 0)
def test_create_group_owned_item_nonexistent_group(self):
reply = client.post('/api/inventory_items/+nonexistent@example.com/', self.f['local_user1'], {
'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private',
})
self.assertEqual(reply.status_code, 404)
self.assertEqual(InventoryItem.objects.count(), 0)
def test_group_items_listed_for_nonexistent_group(self):
reply = client.get('/api/inventory_items/+nonexistent@example.com/', self.f['local_user1'])
self.assertEqual(reply.status_code, 200)
self.assertEqual(len(reply.json()), 0)
def test_put_group_item(self):
# Parity with InventoryApiTestCase.test_put_item, but as a PUT by a different member than
# the creator, to exercise the _is_authorized branch in perform_update for PUT too.
item_id = self.create_group_item().json()['id']
reply = client.put('/api/inventory_items/{}/{}/'.format(self.group_handle, item_id), self.f['local_user2'], {
'availability_policy': 'sell',
'name': 'drill-4000',
'description': 'new description',
'owned_quantity': 100,
'tags': ['tag1', 'tag3'],
'properties': [{'name': 'prop1', 'value': 'value5'}],
})
self.assertEqual(reply.status_code, 200)
item = InventoryItem.objects.get(id=item_id)
self.assertEqual(item.owner_group, self.f['group1'])
self.assertEqual(item.availability_policy, 'sell')
self.assertEqual(item.name, 'drill-4000')
self.assertEqual(item.description, 'new description')
self.assertEqual(item.owned_quantity, 100)
self.assertEqual([t for t in item.tags.all()], [self.f['tag1'], self.f['tag3']])
self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value5'])
def test_patch_group_item_clears_fields(self):
# Parity with InventoryApiTestCase.test_patch_item2 - clearing category/tags/properties.
reply = client.post('/api/inventory_items/{}/'.format(self.group_handle), self.f['local_user1'], {
'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private',
'category': 'cat1', 'tags': ['tag1'],
})
item_id = reply.json()['id']
reply = client.patch('/api/inventory_items/{}/{}/'.format(self.group_handle, item_id), self.f['local_user2'], {
'category': None, 'tags': [], 'properties': []
})
self.assertEqual(reply.status_code, 200)
item = InventoryItem.objects.get(id=item_id)
self.assertEqual(item.category, None)
self.assertEqual([t for t in item.tags.all()], [])
def test_group_item_storage_location(self):
reply = client.post('/api/inventory_items/{}/'.format(self.group_handle), self.f['local_user1'], {
'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private',
'storage_location': self.f['loc1'].id,
})
self.assertEqual(reply.status_code, 201)
item = InventoryItem.objects.get(name='drill')
self.assertEqual(item.storage_location, self.f['loc1'])
def test_post_group_item_with_file_id(self):
# Parity with TestInventoryItemWithFileApiTestCase.test_post_item_with_file_id.
reply = client.post('/api/inventory_items/{}/'.format(self.group_handle), self.f['local_user1'], {
'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private',
'files': [self.f['test_file1'].id],
})
self.assertEqual(reply.status_code, 201)
item = InventoryItem.objects.get(name='drill')
self.assertEqual([f for f in item.files.all()], [self.f['test_file1']])
def test_post_group_item_with_encoded_file(self):
reply = client.post('/api/inventory_items/{}/'.format(self.group_handle), self.f['local_user1'], {
'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private',
'files': [{'data': self.f['encoded_content3'], 'mime_type': 'text/plain'}],
})
self.assertEqual(reply.status_code, 201)
item = InventoryItem.objects.get(name='drill')
self.assertEqual([f for f in item.files.all()], [self.f['test_file3']])
def test_group_items_excluded_from_search(self):
# Group-owned items are reachable only via the group's own detail page for MVP (see
# docs/design-in-progress/groups-mvp.md) - search must not surface them either.
self.create_group_item(name='searchable-drill')
InventoryItem.create_for_owner(owner=self.f['local_user1'], owned_quantity=1, name='searchable-personal')
reply = client.get('/api/search/?query=searchable', self.f['local_user1'])
self.assertEqual(reply.status_code, 200)
names = [item['name'] for item in reply.json()]
self.assertEqual(names, ['searchable-personal'])
class InventoryItemIdAllocationTestCase(UserTestMixin, ToolshedTestCase):
"""InventoryItem.id is sequential, gapless, and never reused within each owner/owner_group's
own items, allocated independently per scope (see OwnerItemSequence)."""
def setUp(self):
super().setUp()
self.prepare_users()
def test_ids_are_sequential_and_independent_per_owner(self):
user1_items = [InventoryItem.create_for_owner(owner=self.f['local_user1'], name=f'u1-{i}')
for i in range(3)]
user2_items = [InventoryItem.create_for_owner(owner=self.f['local_user2'], name=f'u2-{i}')
for i in range(2)]
self.assertEqual([item.id for item in user1_items], [1, 2, 3])
self.assertEqual([item.id for item in user2_items], [1, 2])
def test_deleted_item_id_is_never_reused(self):
item1 = InventoryItem.create_for_owner(owner=self.f['local_user1'], name='first')
item2 = InventoryItem.create_for_owner(owner=self.f['local_user1'], name='second')
self.assertEqual((item1.id, item2.id), (1, 2))
item2.delete() # soft delete - item2's row (and its id) stays in the table
item3 = InventoryItem.create_for_owner(owner=self.f['local_user1'], name='third')
self.assertEqual(item3.id, 3)
self.assertFalse(InventoryItem.objects.filter(owner=self.f['local_user1'], id=2).exists())
self.assertTrue(InventoryItem.global_objects.filter(owner=self.f['local_user1'], id=2).exists())
def test_group_scope_has_independent_sequence(self):
group = Group.objects.create(name='alloc-test-group', domain=self.f['example_com'].name)
personal_item = InventoryItem.create_for_owner(owner=self.f['local_user1'], name='personal')
group_item = InventoryItem.create_for_owner(owner_group=group, name='group-owned')
self.assertEqual(personal_item.id, 1)
self.assertEqual(group_item.id, 1)