GooDrive/backend/drive/test_space_reparent_and_access.py
Louis Mazin e1473076e2 nickel
2026-10-14 17:41:19 +02:00

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())