155 lines
8.6 KiB
Python
155 lines
8.6 KiB
Python
"""Run: USE_SQLITE=true python manage.py test drive.test_space_reparent_and_access"""
|
|
import tempfile
|
|
import uuid
|
|
from pathlib import Path
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.test import TestCase, override_settings
|
|
from rest_framework.test import APIClient
|
|
|
|
from .models import DriveItem, DriveSpace, SpaceGrant
|
|
from .permissions import space_rights
|
|
from .spaces import _make_numbered_space, ensure_space, ensure_private_space
|
|
from .quotas import occupancy
|
|
|
|
|
|
class SpaceReparentingTests(TestCase):
|
|
def setUp(self):
|
|
tmp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(tmp.cleanup)
|
|
self.storage = Path(tmp.name)
|
|
override = override_settings(DRIVE_STORAGE_ROOT=self.storage,
|
|
FILE_UPLOAD_TEMP_DIR=str(self.storage / '.uploads'))
|
|
override.enable()
|
|
self.addCleanup(override.disable)
|
|
User = get_user_model()
|
|
self.admin = User.objects.create_superuser('space-admin', 'admin@example.com', 'password-for-tests')
|
|
self.member = User.objects.create_user('space-member', password='password-for-tests')
|
|
self.client = APIClient()
|
|
self.client.force_authenticate(self.admin)
|
|
self.root = ensure_space(self.admin)
|
|
gb = 1024 ** 3
|
|
self.root.quota_bytes = 100 * gb
|
|
self.root.save(update_fields=['quota_bytes'])
|
|
self.a = _make_numbered_space(self.root, managed_by=self.admin,
|
|
display_name='A', quota_bytes=60 * gb)
|
|
self.b = _make_numbered_space(self.root, managed_by=self.admin,
|
|
display_name='B', quota_bytes=50 * gb)
|
|
self.child = _make_numbered_space(self.a, managed_by=self.admin,
|
|
display_name='Child', quota_bytes=30 * gb)
|
|
self.nested = _make_numbered_space(self.child, managed_by=self.admin,
|
|
display_name='Nested', quota_bytes=10 * gb)
|
|
self.old_path = self.child.relative_path
|
|
self.file = DriveItem.objects.create(owner=self.admin, space=self.nested,
|
|
name='example.pdf', size=15,
|
|
storage_key=str(uuid.uuid4()),
|
|
storage_location=f'{self.nested.relative_path}/example.pdf')
|
|
filepath = self.storage / self.file.storage_location
|
|
filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
filepath.write_bytes(b'important bytes')
|
|
|
|
def test_move_subtree_preserves_files_and_nested_spaces(self):
|
|
res = self.client.patch(f'/api/drive/spaces/{self.child.pk}/',
|
|
{'parent_space': self.b.pk}, format='json')
|
|
self.assertEqual(res.status_code, 200, res.data)
|
|
self.child.refresh_from_db()
|
|
self.nested.refresh_from_db()
|
|
self.file.refresh_from_db()
|
|
expected = f'{self.b.relative_path}/{self.child.directory_name}'
|
|
self.assertEqual(self.child.parent_id, self.b.pk)
|
|
self.assertEqual(self.child.relative_path, expected)
|
|
self.assertEqual(self.nested.relative_path, f'{expected}/{self.nested.directory_name}')
|
|
self.assertEqual(self.file.storage_location,
|
|
f'{self.nested.relative_path}/example.pdf')
|
|
self.assertEqual((self.storage / self.file.storage_location).read_bytes(), b'important bytes')
|
|
self.assertFalse((self.storage / self.old_path).exists())
|
|
self.assertEqual(occupancy(self.b)[0], 15)
|
|
self.assertEqual(occupancy(self.a)[0], 0)
|
|
|
|
def test_move_refused_if_it_would_create_cycle(self):
|
|
res = self.client.patch(f'/api/drive/spaces/{self.a.pk}/',
|
|
{'parent_space': self.nested.pk}, format='json')
|
|
self.assertEqual(res.status_code, 400, res.data)
|
|
self.a.refresh_from_db()
|
|
self.assertEqual(self.a.parent_id, self.root.pk)
|
|
self.assertTrue((self.storage / self.old_path).exists())
|
|
|
|
def test_move_rolls_back_when_new_quota_is_insufficient(self):
|
|
res = self.client.patch(f'/api/drive/spaces/{self.child.pk}/',
|
|
{'parent_space': self.b.pk, 'quota_bytes': 0}, format='json')
|
|
self.assertEqual(res.status_code, 400, res.data)
|
|
self.child.refresh_from_db()
|
|
self.file.refresh_from_db()
|
|
self.assertEqual(self.child.parent_id, self.a.pk)
|
|
self.assertEqual(self.child.relative_path, self.old_path)
|
|
self.assertTrue((self.storage / self.file.storage_location).is_file())
|
|
|
|
def test_manage_space_without_create_right_on_target_cannot_move(self):
|
|
SpaceGrant.objects.create(user=self.member, space=self.child,
|
|
permissions=['view','manage_space'])
|
|
SpaceGrant.objects.create(user=self.member, space=self.b,
|
|
permissions=['view'])
|
|
self.client.force_authenticate(self.member)
|
|
res = self.client.patch(f'/api/drive/spaces/{self.child.pk}/',
|
|
{'parent_space': self.b.pk}, format='json')
|
|
self.assertEqual(res.status_code, 403, res.data)
|
|
self.child.refresh_from_db()
|
|
self.assertEqual(self.child.parent_id, self.a.pk)
|
|
|
|
def test_private_space_keeps_its_separate_physical_storage(self):
|
|
SpaceGrant.objects.create(user=self.member, space=self.child,
|
|
permissions=['view','download'])
|
|
private = ensure_private_space(self.member)
|
|
self.assertTrue(private.relative_path.startswith('privates/'))
|
|
res = self.client.patch(f'/api/drive/spaces/{self.child.pk}/',
|
|
{'parent_space': self.b.pk}, format='json')
|
|
self.assertEqual(res.status_code, 200, res.data)
|
|
private.refresh_from_db()
|
|
self.assertEqual(private.parent_id, self.child.pk)
|
|
self.assertTrue((self.storage/private.relative_path).is_dir())
|
|
|
|
def test_assigned_space_move_does_not_grant_parent_permissions(self):
|
|
assigned = _make_numbered_space(self.a, user=self.member, display_name='Assigned',
|
|
quota_bytes=10 * 1024**3, can_view_parent=True,
|
|
can_write_parent=True, can_invite=True,
|
|
parent_permissions=['view','download','add','manage_access'])
|
|
self.assertIn('manage_access', space_rights(self.member,self.a))
|
|
res = self.client.patch(f'/api/drive/spaces/{assigned.pk}/',
|
|
{'parent_space':self.b.pk}, format='json')
|
|
self.assertEqual(res.status_code,200,res.data)
|
|
assigned.refresh_from_db()
|
|
self.assertFalse(assigned.can_view_parent)
|
|
self.assertFalse(assigned.can_invite)
|
|
self.assertFalse(space_rights(self.member,self.b))
|
|
|
|
def test_move_is_blocked_by_occupied_destination_quota(self):
|
|
huge = 50 * 1024**3
|
|
DriveItem.objects.create(owner=self.admin,space=self.b,name='full.bin',size=huge,
|
|
storage_key=str(uuid.uuid4()),storage_location='legacy-file')
|
|
res = self.client.patch(f'/api/drive/spaces/{self.child.pk}/',
|
|
{'parent_space':self.b.pk},format='json')
|
|
self.assertEqual(res.status_code,400,res.data)
|
|
self.child.refresh_from_db()
|
|
self.assertEqual(self.child.parent_id,self.a.pk)
|
|
self.assertTrue((self.storage/self.old_path).is_dir())
|
|
|
|
def test_admin_edits_direct_and_assigned_access_without_creating_duplicates(self):
|
|
assigned = _make_numbered_space(self.root, user=self.member,
|
|
display_name='Assigned', quota_bytes=10 * 1024**3)
|
|
listing = self.client.get(f'/api/drive/admin/users/{self.member.pk}/access/')
|
|
self.assertEqual(listing.status_code, 200, listing.data)
|
|
self.assertEqual(sum(a['space_id'] == assigned.pk for a in listing.data['accesses']), 1)
|
|
url = f'/api/drive/admin/users/{self.member.pk}/access/owner/{assigned.pk}/'
|
|
saved = self.client.patch(url, {'permissions':['view','download']}, format='json')
|
|
self.assertEqual(saved.status_code, 200, saved.data)
|
|
self.assertEqual(space_rights(self.member, assigned), {'view','download'})
|
|
denied = self.client.patch(url, {'permissions':['modify']}, format='json')
|
|
self.assertEqual(denied.status_code, 400, denied.data)
|
|
listing = self.client.get(f'/api/drive/admin/users/{self.member.pk}/access/')
|
|
self.assertEqual(sum(a['space_id'] == assigned.pk for a in listing.data['accesses']), 1)
|
|
self.assertEqual(next(access['permissions'] for access in listing.data['accesses']
|
|
if access['space_id'] == assigned.pk), ['view','download'])
|
|
revoked = self.client.delete(url)
|
|
self.assertEqual(revoked.status_code, 204)
|
|
self.assertFalse(SpaceGrant.objects.filter(user=self.member,space=assigned).exists())
|