Introduce workspace tenancy boundaries
This commit is contained in:
+87
-10
@@ -9,7 +9,7 @@ from django.test import TestCase, override_settings
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from .models import ApiKey, Attachment, Collection, CollectionSection, Item, Tag, Team, TeamMembership, UserPreference
|
||||
from .models import ApiKey, Attachment, Collection, CollectionSection, Item, Tag, Team, TeamMembership, UserPreference, Workspace, WorkspaceMembership, get_or_create_personal_workspace
|
||||
from .templatetags.markdown_extras import markdown, markdown_title
|
||||
from .views import parse_due_command, parse_quick_content
|
||||
|
||||
@@ -40,6 +40,83 @@ class ItemMetadataTests(TestCase):
|
||||
self.assertFalse(Tag.objects.filter(name='new').exists())
|
||||
|
||||
|
||||
class WorkspaceTenancyTests(TestCase):
|
||||
def test_same_tag_name_is_scoped_per_personal_workspace(self):
|
||||
first_user = get_user_model().objects.create_user(username='tenant-a', password='secret')
|
||||
second_user = get_user_model().objects.create_user(username='tenant-b', password='secret')
|
||||
first = Item.objects.create(owner=first_user, content='One #shared')
|
||||
first.sync_metadata()
|
||||
second = Item.objects.create(owner=second_user, content='Two #shared')
|
||||
second.sync_metadata()
|
||||
|
||||
self.assertEqual(Tag.objects.filter(name='shared').count(), 2)
|
||||
self.assertNotEqual(first.tags.get(name='shared').workspace_id, second.tags.get(name='shared').workspace_id)
|
||||
|
||||
def test_same_team_tag_is_allowed_in_different_workspaces(self):
|
||||
first_user = get_user_model().objects.create_user(username='team-a', password='secret')
|
||||
second_user = get_user_model().objects.create_user(username='team-b', password='secret')
|
||||
|
||||
first = Team.objects.create(owner=first_user, name='Docs A', slug='docs')
|
||||
second = Team.objects.create(owner=second_user, name='Docs B', slug='docs')
|
||||
|
||||
self.assertNotEqual(first.workspace_id, second.workspace_id)
|
||||
|
||||
def test_workspace_can_be_created_and_becomes_active(self):
|
||||
user = get_user_model().objects.create_user(username='workspace-create', password='secret')
|
||||
self.client.force_login(user)
|
||||
|
||||
response = self.client.post(reverse('settings'), {'action': 'workspace', 'name': 'Acme Inc', 'slug': 'acme'})
|
||||
|
||||
workspace = Workspace.objects.get(slug='acme')
|
||||
self.assertRedirects(response, reverse('settings'))
|
||||
self.assertEqual(self.client.session['workspace_id'], workspace.id)
|
||||
self.assertTrue(WorkspaceMembership.objects.filter(workspace=workspace, user=user, role=WorkspaceMembership.Role.OWNER).exists())
|
||||
|
||||
def test_workspace_switch_changes_overview_scope(self):
|
||||
user = get_user_model().objects.create_user(username='workspace-switch', password='secret')
|
||||
personal = get_or_create_personal_workspace(user)
|
||||
company = Workspace.objects.create(owner=user, name='Company', slug='company')
|
||||
WorkspaceMembership.objects.create(workspace=company, user=user, role=WorkspaceMembership.Role.OWNER)
|
||||
Item.objects.create(workspace=personal, owner=user, content='Personal item')
|
||||
Item.objects.create(workspace=company, owner=user, content='Company item')
|
||||
self.client.force_login(user)
|
||||
|
||||
response = self.client.post(reverse('workspace_switch', args=[company.id]), {'next': reverse('index')})
|
||||
self.assertRedirects(response, reverse('index'))
|
||||
response = self.client.get(reverse('index'))
|
||||
|
||||
self.assertContains(response, 'Company item')
|
||||
self.assertNotContains(response, 'Personal item')
|
||||
|
||||
def test_workspace_public_item_is_visible_to_workspace_member_only(self):
|
||||
owner = get_user_model().objects.create_user(username='workspace-owner', password='secret')
|
||||
member = get_user_model().objects.create_user(username='workspace-member', password='secret')
|
||||
outsider = get_user_model().objects.create_user(username='workspace-outsider', password='secret')
|
||||
workspace = Workspace.objects.create(owner=owner, name='Company', slug='company-public')
|
||||
WorkspaceMembership.objects.create(workspace=workspace, user=owner, role=WorkspaceMembership.Role.OWNER)
|
||||
WorkspaceMembership.objects.create(workspace=workspace, user=member, role=WorkspaceMembership.Role.MEMBER)
|
||||
Item.objects.create(workspace=workspace, owner=owner, content='Workspace public', visibility=Item.Visibility.PUBLIC)
|
||||
|
||||
self.client.force_login(member)
|
||||
self.client.post(reverse('workspace_switch', args=[workspace.id]), {'next': reverse('index')})
|
||||
self.assertContains(self.client.get(reverse('index')), 'Workspace public')
|
||||
|
||||
self.client.force_login(outsider)
|
||||
self.assertNotContains(self.client.get(reverse('index')), 'Workspace public')
|
||||
|
||||
def test_authenticated_overview_is_limited_to_current_workspace(self):
|
||||
user = get_user_model().objects.create_user(username='tenant-visible', password='secret')
|
||||
other = get_user_model().objects.create_user(username='tenant-other', password='secret')
|
||||
own = Item.objects.create(owner=user, content='Own item')
|
||||
public_other = Item.objects.create(owner=other, content='Public other item', visibility=Item.Visibility.PUBLIC)
|
||||
self.client.force_login(user)
|
||||
|
||||
response = self.client.get(reverse('index'))
|
||||
|
||||
self.assertContains(response, 'Own item')
|
||||
self.assertNotContains(response, 'Public other item')
|
||||
|
||||
|
||||
class DefaultItemKindTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = get_user_model().objects.create_user(username='tester', password='secret')
|
||||
@@ -262,7 +339,7 @@ class NavigationAndLayoutTests(TestCase):
|
||||
self.assertNotIn('#noteonly', filter_tags)
|
||||
self.assertIn("'openonly'", html)
|
||||
self.assertIn("'noteonly'", html)
|
||||
self.assertIn("'publiconly'", html)
|
||||
self.assertNotIn('publiconly', html)
|
||||
self.assertNotIn('foreignonly', html)
|
||||
|
||||
response = self.client.get(f'{reverse("tag_list")}?status=archive')
|
||||
@@ -283,7 +360,7 @@ class NavigationAndLayoutTests(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
tags = response.json()['tags']
|
||||
self.assertIn('ownapi', tags)
|
||||
self.assertIn('publicapi', tags)
|
||||
self.assertNotIn('publicapi', tags)
|
||||
self.assertNotIn('foreignapi', tags)
|
||||
|
||||
def test_due_filter_also_shows_next_seven_days_separately(self):
|
||||
@@ -482,7 +559,7 @@ class CollectionTests(TestCase):
|
||||
def test_collection_form_separates_teams_and_content_tags(self):
|
||||
team = Team.objects.create(name='Docs team', slug='docs', owner=self.user)
|
||||
TeamMembership.objects.create(team=team, user=self.user, role=TeamMembership.Role.OWNER)
|
||||
Tag.objects.create(name='docs')
|
||||
Tag.objects.create(workspace=get_or_create_personal_workspace(self.user), name='docs')
|
||||
note = Item.objects.create(owner=self.user, content='#ordinary')
|
||||
note.sync_metadata()
|
||||
response = self.client.get(reverse('collection_create'))
|
||||
@@ -801,15 +878,14 @@ class CollectionTests(TestCase):
|
||||
self.assertRedirects(response, manage_url)
|
||||
self.assertTrue(parent.sections.filter(sub_collection=child).exists())
|
||||
|
||||
def test_public_collection_is_visible_without_login(self):
|
||||
def test_public_collection_is_workspace_public_not_anonymous(self):
|
||||
collection = Collection.objects.create(owner=self.user, title='Public', visibility=Item.Visibility.PUBLIC)
|
||||
self.client.logout()
|
||||
response = self.client.get(collection.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotContains(response, reverse('collection_manage', args=[collection.id]))
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_collection_tag_is_not_removed_as_unused(self):
|
||||
tag = Tag.objects.create(name='documentation')
|
||||
tag = Tag.objects.create(workspace=get_or_create_personal_workspace(self.user), name='documentation')
|
||||
collection = Collection.objects.create(owner=self.user, title='Tagged', mode=Collection.Mode.TAGS)
|
||||
collection.filter_tags.add(tag)
|
||||
note = Item.objects.create(owner=self.user, content='#temporary')
|
||||
@@ -835,8 +911,9 @@ class CollectionTests(TestCase):
|
||||
class ApiItemFilterTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = get_user_model().objects.create_user(username='api-filter', password='secret')
|
||||
self.help_tag = Tag.objects.create(name='help')
|
||||
self.api_key = ApiKey.objects.create(owner=self.user, name='Help CLI')
|
||||
self.workspace = get_or_create_personal_workspace(self.user)
|
||||
self.help_tag = Tag.objects.create(workspace=self.workspace, name='help')
|
||||
self.api_key = ApiKey.objects.create(workspace=self.workspace, owner=self.user, name='Help CLI')
|
||||
self.api_key.tags.add(self.help_tag)
|
||||
|
||||
def create_item(self, content):
|
||||
|
||||
Reference in New Issue
Block a user