688 lines
35 KiB
Python
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)
|