stash
This commit is contained in:
parent
bbe52e4a78
commit
54374abf86
16 changed files with 499 additions and 56 deletions
|
|
@ -35,10 +35,10 @@ class InventoryTestMixin(CategoryTestMixin, TagTestMixin, PropertyTestMixin):
|
|||
def prepare_inventory(self):
|
||||
self.f['local_user1'].friends.add(self.f['local_user2'].public_identity)
|
||||
|
||||
self.f['item1'] = InventoryItem.objects.create(
|
||||
self.f['item1'] = InventoryItem.create_for_owner(
|
||||
owner=self.f['local_user1'], owned_quantity=1, name='test1', description='test', category=self.f['cat1'],
|
||||
availability_policy='friends')
|
||||
self.f['item2'] = InventoryItem.objects.create(
|
||||
self.f['item2'] = InventoryItem.create_for_owner(
|
||||
owner=self.f['local_user1'], owned_quantity=1, name='test2', description='test2', category=self.f['cat1'],
|
||||
availability_policy='friends')
|
||||
self.f['item2'].tags.add(self.f['tag1'], through_defaults={})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from django.test import Client
|
||||
from authentication.tests import SignatureAuthClient, UserTestMixin, ToolshedTestCase
|
||||
from authentication.tests import SignatureAuthClient, UserTestMixin, GroupTestMixin, ToolshedTestCase
|
||||
from files.tests import FilesTestMixin
|
||||
from toolshed.models import File
|
||||
from toolshed.models import File, InventoryItem
|
||||
|
||||
from toolshed.tests import InventoryTestMixin
|
||||
|
||||
|
|
@ -156,3 +156,60 @@ class FileApiTestCase(UserTestMixin, FilesTestMixin, InventoryTestMixin, Toolshe
|
|||
self.assertEqual(reply.json()[0]['files'][0]['mime_type'], 'text/plain')
|
||||
self.assertEqual(reply.json()[0]['files'][1]['mime_type'], 'text/plain')
|
||||
self.assertEqual(reply.json()[1]['files'][0]['mime_type'], 'text/plain')
|
||||
|
||||
|
||||
class GroupOwnedFileApiTestCase(UserTestMixin, GroupTestMixin, FilesTestMixin, ToolshedTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.prepare_users()
|
||||
self.prepare_groups()
|
||||
self.prepare_files()
|
||||
self.f['group1'].members.add(self.f['local_user2'].public_identity)
|
||||
self.f['group_item'] = InventoryItem.create_for_owner(
|
||||
owner_group=self.f['group1'], owned_quantity=1, name='group-drill', availability_policy='private')
|
||||
self.f['group_item'].files.add(self.f['test_file1'])
|
||||
|
||||
def test_get_group_item_files(self):
|
||||
response = client.get(f"/api/item_files/{self.f['group_item'].id}/", self.f['local_user1'])
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(response.json()), 1)
|
||||
|
||||
def test_other_member_can_post_file(self):
|
||||
response = client.post(f"/api/item_files/{self.f['group_item'].id}/", self.f['local_user2'],
|
||||
{'data': self.f['encoded_content4'], 'mime_type': 'text/plain'})
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(self.f['group_item'].files.count(), 2)
|
||||
|
||||
def test_remote_member_without_local_account_can_post_file(self):
|
||||
self.f['group1'].members.add(self.f['ext_user1'].public_identity)
|
||||
response = client.post(f"/api/item_files/{self.f['group_item'].id}/", self.f['ext_user1'],
|
||||
{'data': self.f['encoded_content4'], 'mime_type': 'text/plain'})
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(self.f['group_item'].files.count(), 2)
|
||||
|
||||
def test_remote_member_without_local_account_can_get_files(self):
|
||||
self.f['group1'].members.add(self.f['ext_user1'].public_identity)
|
||||
response = client.get(f"/api/item_files/{self.f['group_item'].id}/", self.f['ext_user1'])
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_non_member_cannot_post_file(self):
|
||||
response = client.post(f"/api/item_files/{self.f['group_item'].id}/", self.f['ext_user1'],
|
||||
{'data': self.f['encoded_content4'], 'mime_type': 'text/plain'})
|
||||
self.assertEqual(response.status_code, 404)
|
||||
self.assertEqual(self.f['group_item'].files.count(), 1)
|
||||
|
||||
def test_non_member_cannot_get_files(self):
|
||||
response = client.get(f"/api/item_files/{self.f['group_item'].id}/", self.f['ext_user1'])
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_other_member_can_delete_file(self):
|
||||
response = client.delete(f"/api/item_files/{self.f['group_item'].id}/{self.f['test_file1'].id}/",
|
||||
self.f['local_user2'])
|
||||
self.assertEqual(response.status_code, 204)
|
||||
self.assertEqual(self.f['group_item'].files.count(), 0)
|
||||
|
||||
def test_non_member_cannot_delete_file(self):
|
||||
response = client.delete(f"/api/item_files/{self.f['group_item'].id}/{self.f['test_file1'].id}/",
|
||||
self.f['ext_user1'])
|
||||
self.assertEqual(response.status_code, 404)
|
||||
self.assertEqual(self.f['group_item'].files.count(), 1)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
from authentication.models import Group
|
||||
from authentication.tests import SignatureAuthClient, UserTestMixin, GroupTestMixin, ToolshedTestCase
|
||||
from files.tests import FilesTestMixin
|
||||
from toolshed.models import InventoryItem, Category
|
||||
from toolshed.tests import InventoryTestMixin
|
||||
from toolshed.tests import InventoryTestMixin, CategoryTestMixin, TagTestMixin, PropertyTestMixin, LocationTestMixin
|
||||
|
||||
client = SignatureAuthClient()
|
||||
|
||||
|
|
@ -232,7 +233,7 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
|
|||
self.assertEqual(reply.status_code, 403)
|
||||
|
||||
def test_get_shared_item_private(self):
|
||||
private_item = InventoryItem.objects.create(
|
||||
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'])
|
||||
|
|
@ -336,11 +337,17 @@ class TestInventoryItemWithFileApiTestCase(UserTestMixin, FilesTestMixin, Invent
|
|||
self.assertEqual(reply.status_code, 400)
|
||||
|
||||
|
||||
class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase):
|
||||
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)
|
||||
|
||||
def create_group_item(self, name='drill'):
|
||||
|
|
@ -416,4 +423,162 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTest
|
|||
self.create_group_item()
|
||||
reply = client.get('/api/inventory_items/?group={}'.format(self.f['group1'].id), self.f['ext_user1'])
|
||||
self.assertEqual(reply.status_code, 200)
|
||||
self.assertEqual(len(reply.json()), 0)
|
||||
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 must
|
||||
# attach to a group-owned item exactly the same way they do for a personal one.
|
||||
reply = client.post('/api/inventory_items/', 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'}],
|
||||
'owner_group': self.f['group1'].id,
|
||||
})
|
||||
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 must still apply to group-owned items.
|
||||
reply = client.post('/api/inventory_items/', self.f['local_user1'], {
|
||||
'availability_policy': 'private', 'owned_quantity': 1, 'owner_group': self.f['group1'].id,
|
||||
})
|
||||
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/', self.f['local_user1'], {
|
||||
'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private', 'owner_group': 999999,
|
||||
})
|
||||
self.assertEqual(reply.status_code, 404)
|
||||
self.assertEqual(InventoryItem.objects.count(), 0)
|
||||
|
||||
def test_group_items_listed_for_nonexistent_group_query_param(self):
|
||||
reply = client.get('/api/inventory_items/?group=999999', 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 - full replace, by a different member
|
||||
# than the one who created it, exercising the group_items_id -> _is_authorized branch
|
||||
# in perform_update for a PUT (not just PATCH).
|
||||
item_id = self.create_group_item().json()['id']
|
||||
reply = client.put('/api/inventory_items/{}/'.format(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/', self.f['local_user1'], {
|
||||
'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private',
|
||||
'category': 'cat1', 'tags': ['tag1'],
|
||||
'owner_group': self.f['group1'].id,
|
||||
})
|
||||
item_id = reply.json()['id']
|
||||
reply = client.patch('/api/inventory_items/{}/'.format(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/', self.f['local_user1'], {
|
||||
'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private',
|
||||
'storage_location': self.f['loc1'].id, 'owner_group': self.f['group1'].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/', self.f['local_user1'], {
|
||||
'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private',
|
||||
'files': [self.f['test_file1'].id], 'owner_group': self.f['group1'].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/', self.f['local_user1'], {
|
||||
'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private',
|
||||
'files': [{'data': self.f['encoded_content3'], 'mime_type': 'text/plain'}],
|
||||
'owner_group': self.f['group1'].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_file3']])
|
||||
|
||||
def test_group_items_excluded_from_search(self):
|
||||
# Group-owned items are only ever reachable via the group's own detail page for MVP
|
||||
# (see docs/design-in-progress/groups-mvp.md) - search must not surface them, same as
|
||||
# the main Inventory list already doesn't.
|
||||
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 and gapless within each owner/owner_group's own items,
|
||||
never reused, and 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)
|
||||
|
|
@ -111,7 +111,7 @@ class LocationApiTestCase(UserTestMixin, InventoryTestMixin, LocationTestMixin,
|
|||
self.assertEqual(StorageLocation.objects.filter(id=self.f['loc4'].id).count(), 0)
|
||||
|
||||
def test_delete_location_with_items_sets_null(self):
|
||||
item = InventoryItem.objects.create(
|
||||
item = InventoryItem.create_for_owner(
|
||||
owner=self.f['local_user1'], name='located_item', storage_location=self.f['loc3'])
|
||||
reply = client.delete('/api/storage_locations/' + str(self.f['loc3'].id) + '/', self.f['local_user1'])
|
||||
self.assertEqual(reply.status_code, 204)
|
||||
|
|
|
|||
|
|
@ -28,11 +28,11 @@ class _DeleteTestDataMixin(UserTestMixin, CategoryTestMixin, LocationTestMixin):
|
|||
self.f['orphan_file'] = File.objects.create(
|
||||
file=ContentFile(b'orphan', 'orphan'), mime_type='text/plain', hash='orphan')
|
||||
|
||||
self.f['item1'] = InventoryItem.objects.create(
|
||||
self.f['item1'] = InventoryItem.create_for_owner(
|
||||
owner=self.f['local_user1'], owned_quantity=1, name='item1', category=self.f['cat1'])
|
||||
self.f['item1'].files.add(self.f['orphan_file'])
|
||||
|
||||
self.f['item_other_user'] = InventoryItem.objects.create(
|
||||
self.f['item_other_user'] = InventoryItem.create_for_owner(
|
||||
owner=self.f['local_user2'], owned_quantity=1, name='item2', category=self.f['cat1'])
|
||||
self.f['item_other_user'].files.add(self.f['shared_file'])
|
||||
|
||||
|
|
@ -136,7 +136,7 @@ class ImportInventoryPropertiesTestCase(UserTestMixin, CategoryTestMixin, TagTes
|
|||
self.prepare_properties()
|
||||
|
||||
def test_property_values_with_comma_and_equals_round_trip(self):
|
||||
item = InventoryItem.objects.create(owner=self.f['local_user1'], name='widget')
|
||||
item = InventoryItem.create_for_owner(owner=self.f['local_user1'], name='widget')
|
||||
ItemProperty.objects.create(inventory_item=item, property=self.f['prop1'], value='10cm, 20cm')
|
||||
ItemProperty.objects.create(inventory_item=item, property=self.f['prop2'], value='a=b')
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ class ImportInventoryPropertiesTestCase(UserTestMixin, CategoryTestMixin, TagTes
|
|||
self.assertEqual(values, {'prop1': 'value1', 'prop2': 'value2'})
|
||||
|
||||
def test_item_without_properties_imports_cleanly(self):
|
||||
item = InventoryItem.objects.create(owner=self.f['local_user1'], name='bare item')
|
||||
item = InventoryItem.create_for_owner(owner=self.f['local_user1'], name='bare item')
|
||||
|
||||
csv_bytes = b''.join(rows_to_csv(list(inventory_rows(self.f['local_user1']))))
|
||||
|
||||
|
|
@ -179,7 +179,7 @@ class ImportInventoryPropertiesTestCase(UserTestMixin, CategoryTestMixin, TagTes
|
|||
self.assertEqual(list(new_item.itemproperty_set.all()), [])
|
||||
|
||||
def test_category_and_tags_round_trip(self):
|
||||
item = InventoryItem.objects.create(
|
||||
item = InventoryItem.create_for_owner(
|
||||
owner=self.f['local_user1'], name='cat and tags item', category=self.f['cat1'])
|
||||
item.tags.add(self.f['tag1'], self.f['tag2'], through_defaults={})
|
||||
|
||||
|
|
@ -206,7 +206,7 @@ class ImportInventoryPropertiesTestCase(UserTestMixin, CategoryTestMixin, TagTes
|
|||
self.assertFalse(InventoryItem.objects.filter(owner=self.f['local_user1'], name='ghost widget').exists())
|
||||
|
||||
def test_property_value_with_quote_character_round_trips(self):
|
||||
item = InventoryItem.objects.create(owner=self.f['local_user1'], name='quoted widget')
|
||||
item = InventoryItem.create_for_owner(owner=self.f['local_user1'], name='quoted widget')
|
||||
ItemProperty.objects.create(inventory_item=item, property=self.f['prop1'], value='12" screen')
|
||||
|
||||
csv_bytes = b''.join(rows_to_csv(list(inventory_rows(self.f['local_user1']))))
|
||||
|
|
@ -237,7 +237,7 @@ class ExportImportApiRoundTripTestCase(UserTestMixin, CategoryTestMixin, TagTest
|
|||
def test_export_then_import_preserves_category_tags_and_properties(self):
|
||||
import base64
|
||||
|
||||
item = InventoryItem.objects.create(
|
||||
item = InventoryItem.create_for_owner(
|
||||
owner=self.f['local_user1'], name='drill', description='cordless drill',
|
||||
category=self.f['cat1'], availability_policy='friends', owned_quantity=2)
|
||||
item.tags.add(self.f['tag1'], self.f['tag2'], through_defaults={})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue