From ebd0db4b43068a4d2eee9100458342d8d1a540e1 Mon Sep 17 00:00:00 2001 From: jedi Date: Thu, 27 Aug 2026 18:23:39 +0200 Subject: [PATCH] stash --- backend/authentication/tests/test_auth.py | 12 +-- backend/backend/urls.py | 15 +-- backend/hostadmin/tests.py | 8 +- backend/toolshed/api/files.py | 10 +- backend/toolshed/api/friend.py | 12 +-- backend/toolshed/api/group.py | 34 ++---- backend/toolshed/api/idmap.py | 2 +- backend/toolshed/api/info.py | 22 ++-- backend/toolshed/api/version.py | 17 +++ backend/toolshed/tests/test_api.py | 12 +-- backend/toolshed/tests/test_category.py | 2 +- backend/toolshed/tests/test_files.py | 54 +++++----- backend/toolshed/tests/test_friend.py | 58 +++++----- backend/toolshed/tests/test_group.py | 66 ++++++------ backend/toolshed/tests/test_idmap.py | 40 +++---- backend/toolshed/tests/test_inventory.py | 96 ++++++++--------- backend/toolshed/tests/test_locations.py | 48 ++++----- backend/toolshed/tests/test_offlinedata.py | 14 +-- backend/toolshed/tests/test_property.py | 2 +- backend/toolshed/tests/test_tag.py | 2 +- backend/toolshed/tests/test_workflow_api.py | 4 +- cli-client/toolshed-client.py | 12 +-- .../workflows/BulkItemImportWorkflow.vue | 1 - frontend/src/store.js | 102 +++++++++--------- frontend/src/views/Pairing.vue | 2 +- frontend/src/views/Settings.vue | 2 +- frontend/src/views/settings/Data.vue | 6 +- 27 files changed, 326 insertions(+), 329 deletions(-) create mode 100644 backend/toolshed/api/version.py diff --git a/backend/authentication/tests/test_auth.py b/backend/authentication/tests/test_auth.py index 460095b..488dda0 100644 --- a/backend/authentication/tests/test_auth.py +++ b/backend/authentication/tests/test_auth.py @@ -412,26 +412,26 @@ class FriendApiTestCase(UserTestMixin, ToolshedTestCase): self.client = SignatureAuthClient() def test_friend_local(self): - reply = self.client.get('/api/friends/', self.f['local_user1']) + reply = self.client.get('/api/v1/friends/', self.f['local_user1']) self.assertEqual(reply.status_code, 200) def test_friend_external(self): - reply = self.client.get('/api/friends/', self.f['ext_user1']) + reply = self.client.get('/api/v1/friends/', self.f['ext_user1']) self.assertEqual(reply.status_code, 200) def test_friend_fail(self): - reply = self.anonymous_client.get('/api/friends/') + reply = self.anonymous_client.get('/api/v1/friends/') self.assertEqual(reply.status_code, 403) def test_friend_fail2(self): - target = "/api/friends/" + target = "/api/v1/friends/" signature = self.f['local_user1'].sign("http://testserver2" + target) header = {'HTTP_AUTHORIZATION': 'Signature ' + str(self.f['local_user1']) + ':' + signature} reply = self.anonymous_client.get(target, **header) self.assertEqual(reply.status_code, 403) def test_friend_fail3(self): - target = "/api/friends/" + target = "/api/v1/friends/" unknown_user = DummyExternalUser('extuser3', 'external.org', False) signature = unknown_user.sign("http://testserver" + target) header = {'HTTP_AUTHORIZATION': 'Signature ' + str(unknown_user) + ':' + signature} @@ -439,7 +439,7 @@ class FriendApiTestCase(UserTestMixin, ToolshedTestCase): self.assertEqual(reply.status_code, 403) def test_friend_fail4(self): - target = "/api/friends/" + target = "/api/v1/friends/" signature = self.f['local_user1'].sign("http://testserver" + target) header = {'HTTP_AUTHORIZATION': 'Auth ' + str(self.f['local_user1']) + ':' + signature} reply = self.anonymous_client.get(target, **header) diff --git a/backend/backend/urls.py b/backend/backend/urls.py index 105a175..e9e9dac 100644 --- a/backend/backend/urls.py +++ b/backend/backend/urls.py @@ -19,13 +19,14 @@ urlpatterns = [ path('djangoadmin/', admin.site.urls), path('auth/', include('authentication.api')), path('admin/', include('hostadmin.api')), - path('api/', include('toolshed.api.friend')), - path('api/', include('toolshed.api.group')), - path('api/', include('toolshed.api.idmap')), - path('api/', include('toolshed.api.inventory')), - path('api/', include('toolshed.api.info')), - path('api/', include('toolshed.api.files')), - path('api/', include('toolshed.api.offlinedata')), + path('api/version/', include('toolshed.api.version')), + path('api/v1/', include('toolshed.api.info')), + path('api/v1/', include('toolshed.api.friend')), + path('api/v1/', include('toolshed.api.group')), + path('api/v1/', include('toolshed.api.idmap')), + path('api/v1/', include('toolshed.api.inventory')), + path('api/v1/', include('toolshed.api.files')), + path('api/v1/', include('toolshed.api.offlinedata')), path('media/', include('files.media_urls')), path('docs/', schema_view.with_ui('swagger', cache_timeout=0), name='api-docs'), ] diff --git a/backend/hostadmin/tests.py b/backend/hostadmin/tests.py index 0df45c2..209f933 100644 --- a/backend/hostadmin/tests.py +++ b/backend/hostadmin/tests.py @@ -28,7 +28,7 @@ class DomainApiTestCase(UserTestMixin, ToolshedTestCase): self.prepare_users() def test_get_domains(self): - response = client.get('/api/domains/', self.f['local_user1']) + response = client.get('/api/v1/domains/', self.f['local_user1']) self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), ['example.com']) @@ -97,7 +97,7 @@ class CategoryApiTestCase(UserTestMixin, CategoryTestMixin, ToolshedTestCase): self.prepare_categories() def test_get_categories(self): - response = client.get('/api/categories/', self.f['local_user1']) + response = client.get('/api/v1/categories/', self.f['local_user1']) self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), ["cat1", "cat2", "cat3", "cat1/subcat1", @@ -215,7 +215,7 @@ class TagApiTestCase(UserTestMixin, CategoryTestMixin, TagTestMixin, ToolshedTes self.prepare_tags() def test_get_tags(self): - response = client.get('/api/tags/', self.f['local_user1']) + response = client.get('/api/v1/tags/', self.f['local_user1']) self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), ["tag1", "tag2", "tag3"]) @@ -275,7 +275,7 @@ class PropertyApiTestCase(UserTestMixin, CategoryTestMixin, PropertyTestMixin, T self.prepare_properties() def test_get_properties(self): - response = client.get('/api/properties/', self.f['local_user1']) + response = client.get('/api/v1/properties/', self.f['local_user1']) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 3) self.assertEqual(response.json()[0]['name'], 'prop1') diff --git a/backend/toolshed/api/files.py b/backend/toolshed/api/files.py index b5cc575..675e8e3 100644 --- a/backend/toolshed/api/files.py +++ b/backend/toolshed/api/files.py @@ -29,7 +29,7 @@ def _get_authorized_item(identity, item_id): @api_view(['GET']) @permission_classes([IsAuthenticated]) @authentication_classes([SignatureAuthenticationLocal]) -def list_all_files(request, format=None): # /files/ +def list_all_files(request, format=None): # request.user is a ToolshedUser here; reach group membership via public_identity. files = File.objects.select_related().filter( Q(connected_items__owner=request.user) | @@ -94,7 +94,7 @@ def post_staged_file(request, workflow_id): @api_view(['POST', 'GET']) @permission_classes([IsAuthenticated]) @authentication_classes([SignatureAuthentication]) -def item_files(request, item_id, format=None): # /item_files/ +def item_files(request, item_id, format=None): if request.method == 'GET': return get_item_files(request, item_id) elif request.method == 'POST': @@ -104,7 +104,7 @@ def item_files(request, item_id, format=None): # /item_files/ @api_view(['DELETE']) @permission_classes([IsAuthenticated]) @authentication_classes([SignatureAuthentication]) -def delete_item_file(request, item_id, file_id, format=None): # /item_files/ +def delete_item_file(request, item_id, file_id, format=None): item = _get_authorized_item(request.user, item_id) if item is None: return Response(status=status.HTTP_404_NOT_FOUND) @@ -123,7 +123,7 @@ def delete_item_file(request, item_id, file_id, format=None): # /item_files/ @api_view(['POST', 'GET']) @permission_classes([IsAuthenticated]) @authentication_classes([SignatureAuthenticationLocal]) -def staged_files(request, workflow_id, format=None): # /staged_files/ +def staged_files(request, workflow_id, format=None): if request.method == 'GET': return get_staged_files(request, workflow_id) elif request.method == 'POST': @@ -133,7 +133,7 @@ def staged_files(request, workflow_id, format=None): # /staged_files/ @api_view(['DELETE']) @permission_classes([IsAuthenticated]) @authentication_classes([SignatureAuthenticationLocal]) -def delete_staged_file(request, workflow_id, file_hash, format=None): # /staged_files/ +def delete_staged_file(request, workflow_id, file_hash, format=None): try: workflow = WorkflowInstance.objects.get(id=workflow_id, owner=request.user) file = workflow.staged_files.get(hash=file_hash) diff --git a/backend/toolshed/api/friend.py b/backend/toolshed/api/friend.py index 07ee0fc..539bfb0 100644 --- a/backend/toolshed/api/friend.py +++ b/backend/toolshed/api/friend.py @@ -19,13 +19,13 @@ class Friends(APIView, ViewSetMixin): authentication_classes = [SignatureAuthentication] permission_classes = [IsAuthenticated] - def get(self, request, format=None): # /api/friends/ # + def get(self, request, format=None): user = request.user friends = user.friends.all() serializer = FriendSerializer(friends, many=True) return Response(serializer.data) - def post(self, request, format=None): # /api/friends/ + def post(self, request, format=None): # only for local users try: user = request.user @@ -47,7 +47,7 @@ class Friends(APIView, ViewSetMixin): class FriendsRequests(APIView, ViewSetMixin): - def get(self, request, format=None): # /api/friendrequests/ + def get(self, request, format=None): raw_request = request.body.decode('utf-8') if user := authenticate_request_against_local_users(request, raw_request): friends_requests = user.friend_requests_incoming.all() @@ -56,7 +56,7 @@ class FriendsRequests(APIView, ViewSetMixin): else: return Response(status=status.HTTP_401_UNAUTHORIZED, data={'status': 'unauthorized'}) - def post(self, request, format=None): # /api/friendrequests/ + def post(self, request, format=None): raw_request = request.body.decode('utf-8') if 'befriender' not in request.data or 'befriendee' not in request.data: return Response(status=status.HTTP_400_BAD_REQUEST, data={'status': 'missing parameters'}) @@ -131,7 +131,7 @@ class FriendsRequests(APIView, ViewSetMixin): @api_view(['DELETE']) @authentication_classes([SignatureAuthenticationLocal]) @permission_classes([IsAuthenticated]) -def dropFriend(request, pk, format=None): # /api/friends// +def dropFriend(request, pk, format=None): user = request.user friend = get_object_or_404(user.friends, pk=pk) user.friends.remove(friend) @@ -142,7 +142,7 @@ def dropFriend(request, pk, format=None): # /api/friends// @api_view(['DELETE']) @authentication_classes([SignatureAuthenticationLocal]) @permission_classes([IsAuthenticated]) -def deleteFriendRequest(request, pk, format=None): # /api/friendrequests// +def deleteFriendRequest(request, pk, format=None): user = request.user get_object_or_404(user.friend_requests_incoming, pk=pk).delete() user.save() diff --git a/backend/toolshed/api/group.py b/backend/toolshed/api/group.py index e682934..a970572 100644 --- a/backend/toolshed/api/group.py +++ b/backend/toolshed/api/group.py @@ -21,11 +21,11 @@ class Groups(APIView, ViewSetMixin): authentication_classes = [SignatureAuthentication] permission_classes = [IsAuthenticated] - def get(self, request, format=None): # /api/groups/ + def get(self, request, format=None): groups = request.user.member_of_groups.all() return Response(GroupSerializer(groups, many=True).data) - def post(self, request, format=None): # /api/groups/ + def post(self, request, format=None): name = request.data.get('name') if not name: return Response(status=status.HTTP_400_BAD_REQUEST, data={'status': 'name is required'}) @@ -40,7 +40,7 @@ class GroupDetail(APIView, ViewSetMixin): authentication_classes = [SignatureAuthentication] permission_classes = [IsAuthenticated] - def get(self, request, handle, format=None): # /api/groups// + def get(self, request, handle, format=None): try: name, domain = split_userhandle_or_throw(handle) except ValueError: @@ -54,7 +54,7 @@ class GroupDetail(APIView, ViewSetMixin): @api_view(['DELETE']) @authentication_classes([SignatureAuthentication]) @permission_classes([IsAuthenticated]) -def removeGroupMember(request, handle, identity_id, format=None): # /api/groups//members// +def removeGroupMember(request, handle, identity_id, format=None): try: name, domain = split_userhandle_or_throw(handle) except ValueError: @@ -73,7 +73,7 @@ def removeGroupMember(request, handle, identity_id, format=None): # /api/groups @api_view(['POST']) @authentication_classes([SignatureAuthentication]) @permission_classes([IsAuthenticated]) -def createGroupInvite(request, handle, format=None): # /api/groups//invites/ +def createGroupInvite(request, handle, format=None): try: name, domain = split_userhandle_or_throw(handle) except ValueError: @@ -97,18 +97,14 @@ def createGroupInvite(request, handle, format=None): # /api/groups// +def declineGroupInvite(request, pk, format=None): get_object_or_404(request.user.group_invites_incoming, pk=pk).delete() return Response(status=status.HTTP_204_NO_CONTENT) @@ -143,12 +139,7 @@ def declineGroupInvite(request, pk, format=None): # /api/groupinvites// @api_view(['POST']) @authentication_classes([SignatureAuthenticationLocal]) @permission_classes([IsAuthenticated]) -def recordGroupMembership(request, pk, format=None): # /api/groupinvites//accept/ - """Local bookkeeping call on the invitee's own home backend, made by the client once its - accept request against the group's home backend (acceptGroupInvite) has succeeded. Turns the - now-obsolete GroupInviteIncoming into a durable GroupMembership pointer, so this backend - remembers which foreign groups its own user belongs to -- the same way FriendRequestIncoming - ends up recorded as a friend on both sides, not just the group's authoritative backend.""" +def recordGroupMembership(request, pk, format=None): invite = get_object_or_404(request.user.group_invites_incoming, pk=pk) membership, _ = GroupMembership.objects.get_or_create( user=request.user, group_name=invite.group_name, group_domain=invite.group_domain) @@ -157,19 +148,16 @@ def recordGroupMembership(request, pk, format=None): # /api/groupinvites//a class GroupMemberships(APIView, ViewSetMixin): - """/api/groupmemberships/ - the personal index of every group (local or remote) this backend - has recorded the caller as belonging to, kept independently of the group's own membership - roster (see GroupMembership and recordGroupMembership).""" authentication_classes = [SignatureAuthenticationLocal] permission_classes = [IsAuthenticated] - def get(self, request, format=None): # /api/groupmemberships/ + def get(self, request, format=None): memberships = request.user.group_memberships.all() return Response(GroupMembershipSerializer(memberships, many=True).data) @api_view(['POST']) -def acceptGroupInvite(request, format=None): # /api/group_invites/accept/ - lands on the group's home backend +def acceptGroupInvite(request, format=None): raw_request = request.body.decode('utf-8') for field in ('group', 'invitee', 'invitee_key', 'secret'): if field not in request.data: diff --git a/backend/toolshed/api/idmap.py b/backend/toolshed/api/idmap.py index 24ba670..a6451f1 100644 --- a/backend/toolshed/api/idmap.py +++ b/backend/toolshed/api/idmap.py @@ -15,7 +15,7 @@ class IdMap(APIView, ViewSetMixin): authentication_classes = [SignatureAuthentication] permission_classes = [IsAuthenticated] - def get(self, request, format=None): # /api/idmap/ + def get(self, request, format=None): identity = request.user identities = identity.friends.all() | KnownIdentity.objects.filter(pk=identity.pk) groups = identity.member_of_groups.all() diff --git a/backend/toolshed/api/info.py b/backend/toolshed/api/info.py index a99b398..6fc5036 100644 --- a/backend/toolshed/api/info.py +++ b/backend/toolshed/api/info.py @@ -7,20 +7,12 @@ from hostadmin.models import Domain from authentication.signature_auth import SignatureAuthentication from toolshed.models import Tag, Property, Category, InventoryItem from toolshed.serializers import CategorySerializer, PropertySerializer -from backend.settings import TOOLSHED_VERSION, GIT_COMMIT @api_view(['GET']) @permission_classes([]) @authentication_classes([]) -def get_version(request, format=None): # /version/ - return Response({'version': TOOLSHED_VERSION, 'commit': GIT_COMMIT}) - - -@api_view(['GET']) -@permission_classes([]) -@authentication_classes([]) -def list_domains(request, format=None): # /domains/ +def list_domains(request, format=None): domains = [domain.name for domain in Domain.objects.filter(open_registration=True)] return Response(domains) @@ -28,7 +20,7 @@ def list_domains(request, format=None): # /domains/ @api_view(['GET']) @permission_classes([IsAuthenticated]) @authentication_classes([SignatureAuthentication]) -def list_tags(format=None): # /tags/ +def list_tags(format=None): tags = [tag.name for tag in Tag.objects.all()] return Response(tags) @@ -36,28 +28,28 @@ def list_tags(format=None): # /tags/ @api_view(['GET']) @permission_classes([IsAuthenticated]) @authentication_classes([SignatureAuthentication]) -def list_properties(request, format=None): # /properties/ +def list_properties(request, format=None): return Response(PropertySerializer(Property.objects.all(), many=True).data) @api_view(['GET']) @permission_classes([IsAuthenticated]) @authentication_classes([SignatureAuthentication]) -def list_categories(request, format=None): # /categories/ +def list_categories(request, format=None): return Response(CategorySerializer(Category.objects.all(), many=True).data) @api_view(['GET']) @permission_classes([IsAuthenticated]) @authentication_classes([SignatureAuthentication]) -def list_availability_policies(request, format=None): # /availability_policies/ +def list_availability_policies(request, format=None): return Response(InventoryItem.AVAILABILITY_POLICY_CHOICES) @api_view(['GET']) @permission_classes([IsAuthenticated]) @authentication_classes([SignatureAuthentication]) -def combined_info(request, format=None): # /info/ +def combined_info(request, format=None): tags = [tag.name for tag in Tag.objects.all()] properties = PropertySerializer(Property.objects.all(), many=True).data categories = [str(category) for category in Category.objects.all()] @@ -69,7 +61,7 @@ def combined_info(request, format=None): # /info/ urlpatterns = [ - path('version/', get_version, name='version'), + path('availability_policies/', list_availability_policies, name='availability_policies'), path('properties/', list_properties, name='propertylist'), path('categories/', list_categories, name='categorylist'), diff --git a/backend/toolshed/api/version.py b/backend/toolshed/api/version.py new file mode 100644 index 0000000..316332b --- /dev/null +++ b/backend/toolshed/api/version.py @@ -0,0 +1,17 @@ +from django.urls import path +from rest_framework.decorators import api_view, permission_classes, authentication_classes +from rest_framework.response import Response + +from backend.settings import TOOLSHED_VERSION, GIT_COMMIT + + +@api_view(['GET']) +@permission_classes([]) +@authentication_classes([]) +def get_api_version(request, format=None): + return Response({'version': TOOLSHED_VERSION, 'commit': GIT_COMMIT}) + + +urlpatterns = [ + path('', get_api_version, name='version'), +] diff --git a/backend/toolshed/tests/test_api.py b/backend/toolshed/tests/test_api.py index b4101ce..69c25df 100644 --- a/backend/toolshed/tests/test_api.py +++ b/backend/toolshed/tests/test_api.py @@ -27,31 +27,31 @@ class CombinedApiTestCase(UserTestMixin, CategoryTestMixin, TagTestMixin, Proper self.assertEqual(response.json(), {'version': settings.TOOLSHED_VERSION, 'commit': settings.GIT_COMMIT}) def test_domains_anonymous(self): - response = anonymous_client.get('/api/domains/') + response = anonymous_client.get('/api/v1/domains/') self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), ['example.com']) def test_domains_authenticated(self): - response = client.get('/api/domains/', self.f['local_user1']) + response = client.get('/api/v1/domains/', self.f['local_user1']) self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), ['example.com']) def test_policy_api_anonymous(self): - response = anonymous_client.get('/api/availability_policies/') + response = anonymous_client.get('/api/v1/availability_policies/') self.assertEqual(response.status_code, 403) def test_policy_api(self): - response = client.get('/api/availability_policies/', self.f['local_user1']) + response = client.get('/api/v1/availability_policies/', self.f['local_user1']) self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), [['sell', 'Sell'], ['rent', 'Rent'], ['lend', 'Lend'], ['share', 'Share'], ['private', 'Private']]) def test_combined_api_anonymous(self): - response = anonymous_client.get('/api/info/') + response = anonymous_client.get('/api/v1/info/') self.assertEqual(response.status_code, 403) def test_combined_api(self): - response = client.get('/api/info/', self.f['local_user1']) + response = client.get('/api/v1/info/', self.f['local_user1']) self.assertEqual(response.status_code, 200) self.assertEqual(response.json()['availability_policies'], [['sell', 'Sell'], ['rent', 'Rent'], ['lend', 'Lend'], ['share', 'Share'], ['private', 'Private']]) diff --git a/backend/toolshed/tests/test_category.py b/backend/toolshed/tests/test_category.py index 10a552c..2e4d310 100644 --- a/backend/toolshed/tests/test_category.py +++ b/backend/toolshed/tests/test_category.py @@ -32,7 +32,7 @@ class CategoryApiTestCase(CategoryTestMixin, UserTestMixin, ToolshedTestCase): self.prepare_categories() def test_get_categories(self): - reply = client.get('/api/categories/', self.f['local_user1']) + reply = client.get('/api/v1/categories/', self.f['local_user1']) self.assertEqual(reply.status_code, 200) self.assertEqual(len(reply.json()), 7) self.assertEqual(reply.json()[0], 'cat1') diff --git a/backend/toolshed/tests/test_files.py b/backend/toolshed/tests/test_files.py index 1aa1356..8b0f698 100644 --- a/backend/toolshed/tests/test_files.py +++ b/backend/toolshed/tests/test_files.py @@ -25,11 +25,11 @@ class FileApiTestCase(UserTestMixin, FilesTestMixin, InventoryTestMixin, Toolshe self.f['item2'].files.add(self.f['test_file1']) def test_files_anonymous(self): - response = anonymous_client.get(f"/api/item_files/{self.f['item1'].id}/") + response = anonymous_client.get(f"/api/v1/item_files/{self.f['item1'].id}/") self.assertEqual(response.status_code, 403) def test_list_all_files(self): - response = client.get(f"/api/files/", self.f['local_user1']) + response = client.get(f"/api/v1/files/", self.f['local_user1']) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 2) self.assertEqual(response.json()[0]['mime_type'], 'text/plain') @@ -40,7 +40,7 @@ class FileApiTestCase(UserTestMixin, FilesTestMixin, InventoryTestMixin, Toolshe f"/media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}") def test_files(self): - response = client.get(f"/api/item_files/{self.f['item1'].id}/", self.f['local_user1']) + response = client.get(f"/api/v1/item_files/{self.f['item1'].id}/", self.f['local_user1']) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 2) self.assertEqual(response.json()[0]['mime_type'], 'text/plain') @@ -51,11 +51,11 @@ class FileApiTestCase(UserTestMixin, FilesTestMixin, InventoryTestMixin, Toolshe f"/media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}") def test_files_not_found(self): - response = client.get(f"/api/item_files/99999/", self.f['local_user1']) + response = client.get(f"/api/v1/item_files/99999/", self.f['local_user1']) self.assertEqual(response.status_code, 404) def test_post_file(self): - response = client.post(f"/api/item_files/{self.f['item1'].id}/", self.f['local_user1'], + response = client.post(f"/api/v1/item_files/{self.f['item1'].id}/", self.f['local_user1'], {'data': self.f['encoded_content4'], 'mime_type': 'text/plain'}) self.assertEqual(response.status_code, 201) self.assertEqual(File.objects.count(), 4) @@ -68,7 +68,7 @@ class FileApiTestCase(UserTestMixin, FilesTestMixin, InventoryTestMixin, Toolshe def test_post_file_duplicate(self): self.assertEqual(File.objects.count(), 3) self.assertEqual(self.f['item1'].files.count(), 2) - response = client.post(f"/api/item_files/{self.f['item1'].id}/", self.f['local_user1'], + response = client.post(f"/api/v1/item_files/{self.f['item1'].id}/", self.f['local_user1'], {'data': self.f['encoded_content3'], 'mime_type': 'text/plain'}) self.assertEqual(response.status_code, 201) self.assertEqual(File.objects.count(), 3) @@ -79,69 +79,69 @@ class FileApiTestCase(UserTestMixin, FilesTestMixin, InventoryTestMixin, Toolshe f"{self.f['hash3'][:2]}/{self.f['hash3'][2:4]}/{self.f['hash3'][4:6]}/{self.f['hash3'][6:]}") def test_post_file_invalid(self): - response = client.post(f"/api/item_files/{self.f['item1'].id}/", self.f['local_user1'], + response = client.post(f"/api/v1/item_files/{self.f['item1'].id}/", self.f['local_user1'], {'data': self.f['encoded_content4']}) self.assertEqual(response.status_code, 400) def test_post_file_not_found_item(self): - response = client.post(f"/api/item_files/99999/", self.f['local_user1'], + response = client.post(f"/api/v1/item_files/99999/", self.f['local_user1'], {'data': self.f['encoded_content3'], 'mime_type': 'text/plain'}) self.assertEqual(response.status_code, 404) self.assertEqual(File.objects.count(), 3) def test_post_file_not_authenticated(self): - response = anonymous_client.post(f"/api/item_files/{self.f['item1'].id}/", + response = anonymous_client.post(f"/api/v1/item_files/{self.f['item1'].id}/", {'data': self.f['encoded_content3'], 'mime_type': 'text/plain'}) self.assertEqual(response.status_code, 403) self.assertEqual(File.objects.count(), 3) def test_post_file_not_authorized(self): - response = client.post(f"/api/item_files/{self.f['item1'].id}/", self.f['local_user2'], + response = client.post(f"/api/v1/item_files/{self.f['item1'].id}/", self.f['local_user2'], {'data': self.f['encoded_content3'], 'mime_type': 'text/plain'}) self.assertEqual(response.status_code, 404) self.assertEqual(File.objects.count(), 3) def test_delete_file(self): - response = client.delete(f"/api/item_files/{self.f['item1'].id}/{self.f['test_file1'].id}/", + response = client.delete(f"/api/v1/item_files/{self.f['item1'].id}/{self.f['test_file1'].id}/", self.f['local_user1']) self.assertEqual(response.status_code, 204) self.assertEqual(File.objects.count(), 3) self.assertEqual(self.f['item1'].files.count(), 1) def test_delete_file_last_use(self): - response = client.delete(f"/api/item_files/{self.f['item1'].id}/{self.f['test_file2'].id}/", + response = client.delete(f"/api/v1/item_files/{self.f['item1'].id}/{self.f['test_file2'].id}/", self.f['local_user1']) self.assertEqual(response.status_code, 204) self.assertEqual(File.objects.count(), 2) self.assertEqual(self.f['item1'].files.count(), 1) def test_delete_file_not_found(self): - response = client.delete(f"/api/item_files/{self.f['item1'].id}/99999/", self.f['local_user1']) + response = client.delete(f"/api/v1/item_files/{self.f['item1'].id}/99999/", self.f['local_user1']) self.assertEqual(response.status_code, 404) self.assertEqual(File.objects.count(), 3) self.assertEqual(self.f['item1'].files.count(), 2) def test_delete_file_not_found_item(self): - response = client.delete(f"/api/item_files/99999/{self.f['test_file1'].id}/", self.f['local_user1']) + response = client.delete(f"/api/v1/item_files/99999/{self.f['test_file1'].id}/", self.f['local_user1']) self.assertEqual(response.status_code, 404) self.assertEqual(File.objects.count(), 3) self.assertEqual(self.f['item1'].files.count(), 2) def test_delete_file_not_owner(self): - response = client.delete(f"/api/item_files/{self.f['item1'].id}/{self.f['test_file1'].id}/", + response = client.delete(f"/api/v1/item_files/{self.f['item1'].id}/{self.f['test_file1'].id}/", self.f['local_user2']) self.assertEqual(response.status_code, 404) self.assertEqual(File.objects.count(), 3) self.assertEqual(self.f['item1'].files.count(), 2) def test_delete_file_anonymous(self): - response = anonymous_client.delete(f"/api/item_files/{self.f['item1'].id}/{self.f['test_file1'].id}/") + response = anonymous_client.delete(f"/api/v1/item_files/{self.f['item1'].id}/{self.f['test_file1'].id}/") self.assertEqual(response.status_code, 403) self.assertEqual(File.objects.count(), 3) self.assertEqual(self.f['item1'].files.count(), 2) def test_get_inventory(self): - reply = client.get('/api/inventory_items/{}/'.format(self.f['local_user1']), self.f['local_user1']) + reply = client.get('/api/v1/inventory_items/{}/'.format(self.f['local_user1']), self.f['local_user1']) self.assertEqual(reply.status_code, 200) self.assertEqual(len(reply.json()), 2) self.assertEqual(reply.json()[0]['name'], 'test1') @@ -171,46 +171,46 @@ class GroupOwnedFileApiTestCase(UserTestMixin, GroupTestMixin, FilesTestMixin, T 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']) + response = client.get(f"/api/v1/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'], + response = client.post(f"/api/v1/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'], + response = client.post(f"/api/v1/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']) + response = client.get(f"/api/v1/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'], + response = client.post(f"/api/v1/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']) + response = client.get(f"/api/v1/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}/", + response = client.delete(f"/api/v1/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}/", + response = client.delete(f"/api/v1/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) @@ -222,12 +222,12 @@ class GroupOwnedFileApiTestCase(UserTestMixin, GroupTestMixin, FilesTestMixin, T owner_group=group2, owned_quantity=1, name='group2-drill', availability_policy='private') self.assertEqual(self.f['group_item'].id, group2_item.id) - response = client.get(f"/api/item_files/{self.f['group_item'].id}/", self.f['local_user2']) + response = client.get(f"/api/v1/item_files/{self.f['group_item'].id}/", self.f['local_user2']) self.assertEqual(response.status_code, 200) def test_group_item_files_listed_in_all_files(self): - response = client.get('/api/files/', self.f['local_user2']) + response = client.get('/api/v1/files/', self.f['local_user2']) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 1) self.assertEqual(response.json()[0]['id'], self.f['test_file1'].id) diff --git a/backend/toolshed/tests/test_friend.py b/backend/toolshed/tests/test_friend.py index cb0bf1e..88d85d9 100644 --- a/backend/toolshed/tests/test_friend.py +++ b/backend/toolshed/tests/test_friend.py @@ -60,26 +60,26 @@ class FriendApiTestCase(UserTestMixin, ToolshedTestCase): self.f['ext_user1'].friends.add(self.f['local_user1'].public_identity) def test_friend_list_internal1(self): - reply = client.get('/api/friends/', self.f['local_user1']) + reply = client.get('/api/v1/friends/', self.f['local_user1']) self.assertEqual(reply.status_code, 200) self.assertEqual(len(reply.json()), 2) self.assertEqual(reply.json()[0]['handle'], str(self.f['local_user2'])) self.assertEqual(reply.json()[1]['handle'], str(self.f['ext_user1'])) def test_friend_list_internal2(self): - reply = client.get('/api/friends/', self.f['local_user2']) + reply = client.get('/api/v1/friends/', self.f['local_user2']) self.assertEqual(reply.status_code, 200) self.assertEqual(len(reply.json()), 1) self.assertEqual(reply.json()[0]['handle'], str(self.f['local_user1'])) def test_friend_list_external(self): - reply = client.get('/api/friends/', self.f['ext_user1']) + reply = client.get('/api/v1/friends/', self.f['ext_user1']) self.assertEqual(reply.status_code, 200) self.assertEqual(len(reply.json()), 1) self.assertEqual(reply.json()[0]['handle'], str(self.f['local_user1'])) def test_friend_delete(self): - reply = client.delete('/api/friends/{}/'.format(self.f['local_user2'].public_identity.id), + reply = client.delete('/api/v1/friends/{}/'.format(self.f['local_user2'].public_identity.id), self.f['local_user1']) self.assertEqual(reply.status_code, 204) self.assertEqual(self.f['local_user1'].friends.count(), 1) @@ -101,29 +101,29 @@ class FriendRequestListTestCase(UserTestMixin, ToolshedTestCase): self.friendrequest1.save() def test_friend_request_withouth_auth(self): - reply = Client().get('/api/friendrequests/') + reply = Client().get('/api/v1/friendrequests/') self.assertEqual(reply.status_code, 401) def test_friend_request_empty(self): - reply = client.get('/api/friendrequests/', self.f['local_user2']) + reply = client.get('/api/v1/friendrequests/', self.f['local_user2']) self.assertEqual(reply.status_code, 200) self.assertEqual(reply.json(), []) def test_friend_request_list(self): - reply = client.get('/api/friendrequests/', self.f['local_user1']) + reply = client.get('/api/v1/friendrequests/', self.f['local_user1']) self.assertEqual(reply.status_code, 200) self.assertEqual(len(reply.json()), 1) self.assertEqual(reply.json()[0]['befriender'], str(self.f['ext_user2'])) self.assertEqual(reply.json()[0]['befriender_public_key'], self.f['ext_user2'].public_key()) def test_delete_friend_request(self): - reply = client.delete('/api/friendrequests/{}/'.format(self.friendrequest1.id), + reply = client.delete('/api/v1/friendrequests/{}/'.format(self.friendrequest1.id), self.f['local_user1']) self.assertEqual(reply.status_code, 204) self.assertEqual(FriendRequestIncoming.objects.count(), 0) def test_delete_friend_request_not_found(self): - reply = client.delete('/api/friendrequests/999/', self.f['local_user1']) + reply = client.delete('/api/v1/friendrequests/999/', self.f['local_user1']) self.assertEqual(reply.status_code, 404) self.assertEqual(FriendRequestIncoming.objects.count(), 1) @@ -140,7 +140,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase): def test_post_request(self): befriender = self.f['ext_user1'] befriendee = self.f['local_user1'] - reply = client.post('/api/friendrequests/', befriender, { + reply = client.post('/api/v1/friendrequests/', befriender, { 'befriender': str(befriender), 'befriender_key': befriender.public_key(), 'befriendee': str(befriendee), @@ -158,7 +158,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase): def test_post_request_local(self): befriender = self.f['local_user2'] befriendee = self.f['local_user1'] - reply = client.post('/api/friendrequests/', befriender, { + reply = client.post('/api/v1/friendrequests/', befriender, { 'befriender': str(befriender), 'befriendee': str(befriendee), # 'secret': 'secret2' @@ -173,14 +173,14 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase): # self.assertEqual(incoming.secret, 'secret2') def test_post_request_withouth_auth(self): - reply = Client().post('/api/friendrequests/') + reply = Client().post('/api/v1/friendrequests/') self.assertEqual(reply.status_code, 400) def test_post_request_broken_header(self): befriender = self.f['ext_user1'] befriendee = self.f['local_user1'] broken_client = SignatureAuthClient(header_prefix='broken ') - reply = broken_client.post('/api/friendrequests/', befriender, { + reply = broken_client.post('/api/v1/friendrequests/', befriender, { 'befriender': str(befriender), 'befriender_key': befriender.public_key(), 'befriendee': str(befriendee), @@ -191,7 +191,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase): def test_post_request_missing_key(self): befriender = self.f['ext_user1'] befriendee = self.f['local_user1'] - reply = client.post('/api/friendrequests/', befriender, { + reply = client.post('/api/v1/friendrequests/', befriender, { 'befriender': str(befriender), 'befriendee': str(befriendee), 'secret': 'secret2' @@ -201,7 +201,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase): def test_post_request_missing_key_none(self): befriender = self.f['ext_user1'] befriendee = self.f['local_user1'] - reply = client.post('/api/friendrequests/', befriender, { + reply = client.post('/api/v1/friendrequests/', befriender, { 'befriender': str(befriender), 'befriendee': str(befriendee), 'befriender_key': None, @@ -212,7 +212,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase): def test_post_request_breaking_key(self): befriender = self.f['ext_user1'] befriendee = self.f['local_user1'] - reply = client.post('/api/friendrequests/', befriender, { + reply = client.post('/api/v1/friendrequests/', befriender, { 'befriender': str(befriender), 'befriendee': str(befriendee), 'secret': 'secret2', @@ -223,7 +223,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase): def test_post_request_wrong_befriender(self): befriender = self.f['ext_user1'] befriendee = self.f['local_user1'] - reply = client.post('/api/friendrequests/', befriender, { + reply = client.post('/api/v1/friendrequests/', befriender, { 'befriender': str(self.f['local_user2']), 'befriender_key': befriender.public_key(), 'befriendee': str(befriendee), @@ -235,7 +235,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase): befriender = self.f['ext_user1'] befriendee = self.f['local_user1'] bad_signature = SignatureAuthClient(bad_signature=True) - reply = bad_signature.post('/api/friendrequests/', befriender, { + reply = bad_signature.post('/api/v1/friendrequests/', befriender, { 'befriender': str(befriender), 'befriender_key': befriender.public_key(), 'befriendee': str(befriendee), @@ -246,7 +246,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase): def test_post_request_self(self): befriender = self.f['local_user1'] befriendee = self.f['local_user1'] - reply = client.post('/api/friendrequests/', befriender, { + reply = client.post('/api/v1/friendrequests/', befriender, { 'befriender': str(befriender), 'befriender_key': befriender.public_key(), 'befriendee': str(befriendee), @@ -257,7 +257,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase): def test_post_request_befreindee_not_found(self): befriender = self.f['ext_user1'] befriendee = self.f['local_user1'] - reply = client.post('/api/friendrequests/', befriender, { + reply = client.post('/api/v1/friendrequests/', befriender, { 'befriender': str(befriender), 'befriender_key': befriender.public_key(), 'befriendee': 'nonexistent@' + befriendee.domain, @@ -268,7 +268,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase): def test_post_request_missing_secret(self): befriender = self.f['ext_user1'] befriendee = self.f['local_user1'] - reply = client.post('/api/friendrequests/', befriender, { + reply = client.post('/api/v1/friendrequests/', befriender, { 'befriender': str(befriender), 'befriender_key': befriender.public_key(), 'befriendee': str(befriendee) @@ -281,7 +281,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase): request = FriendRequestIncoming.objects.filter(befriender_username=befriender.username, befriender_domain=befriender.domain, befriendee_user=befriendee).first() - reply = client.post('/api/friends/', befriendee, { + reply = client.post('/api/v1/friends/', befriendee, { 'friend_request_id': request.id, 'secret': request.secret }) @@ -294,7 +294,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase): request = FriendRequestIncoming.objects.filter(befriender_username=befriender.username, befriender_domain=befriender.domain, befriendee_user=befriendee).first() - reply = client.post('/api/friends/', befriendee, { + reply = client.post('/api/v1/friends/', befriendee, { 'friend_request_id': request.id, 'secret': request.secret }) @@ -304,7 +304,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase): def test_accept_request_not_found(self): befriender = self.f['ext_user2'] befriendee = self.f['local_user1'] - reply = client.post('/api/friends/', befriendee, { + reply = client.post('/api/v1/friends/', befriendee, { 'friend_request_id': 999, 'secret': 'secret1' }) @@ -326,7 +326,7 @@ class FriendRequestOutgoingTestCase(UserTestMixin, ToolshedTestCase): def test_post_outgoing_friend_request(self): befriender = self.f['local_user1'] befriendee = self.f['ext_user1'] - reply = client.post('/api/friendrequests/', befriender, { + reply = client.post('/api/v1/friendrequests/', befriender, { 'befriender': str(befriender), 'befriendee': str(befriendee), }) @@ -343,7 +343,7 @@ class FriendRequestOutgoingTestCase(UserTestMixin, ToolshedTestCase): def test_accept_request(self): befriender = self.f['ext_user1'] befriendee = self.f['local_user2'] - reply = client.post('/api/friendrequests/', befriender, { + reply = client.post('/api/v1/friendrequests/', befriender, { 'befriender': str(befriender), 'befriender_key': befriender.public_key(), 'befriendee': str(befriendee), @@ -367,12 +367,12 @@ class FriendRequestCombinedTestCase(UserTestMixin, ToolshedTestCase): def test_friend_request_combined(self): befriender = self.f['local_user1'] befriendee = self.f['local_user2'] - reply1 = client.post('/api/friendrequests/', befriender, { + reply1 = client.post('/api/v1/friendrequests/', befriender, { 'befriender': str(befriender), 'befriendee': str(befriendee), }) secret = reply1.json()['secret'] - reply2 = client.post('/api/friendrequests/', befriender, { + reply2 = client.post('/api/v1/friendrequests/', befriender, { 'befriender': str(befriender), 'befriender_key': befriender.public_key(), 'befriendee': str(befriendee), @@ -389,7 +389,7 @@ class FriendRequestCombinedTestCase(UserTestMixin, ToolshedTestCase): befriender = self.f['local_user1'] befriendee = self.f['local_user2'] befriender.friends.add(befriendee.public_identity) - reply1 = client.post('/api/friendrequests/', befriender, { + reply1 = client.post('/api/v1/friendrequests/', befriender, { 'befriender': str(befriender), 'befriendee': str(befriendee), }) diff --git a/backend/toolshed/tests/test_group.py b/backend/toolshed/tests/test_group.py index f3f1405..5a5c9a8 100644 --- a/backend/toolshed/tests/test_group.py +++ b/backend/toolshed/tests/test_group.py @@ -27,34 +27,34 @@ class GroupApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase): self.prepare_users() def test_create_group(self): - reply = client.post('/api/groups/', self.f['local_user1'], {'name': 'workshop'}) + reply = client.post('/api/v1/groups/', self.f['local_user1'], {'name': 'workshop'}) self.assertEqual(reply.status_code, 201) group = Group.objects.get(name='workshop') self.assertEqual(group.domain, self.f['local_user1'].domain) self.assertTrue(group.is_member(self.f['local_user1'].public_identity)) def test_create_group_duplicate_name(self): - client.post('/api/groups/', self.f['local_user1'], {'name': 'workshop'}) - reply = client.post('/api/groups/', self.f['local_user1'], {'name': 'workshop'}) + client.post('/api/v1/groups/', self.f['local_user1'], {'name': 'workshop'}) + reply = client.post('/api/v1/groups/', self.f['local_user1'], {'name': 'workshop'}) self.assertEqual(reply.status_code, 409) def test_create_group_missing_name(self): - reply = client.post('/api/groups/', self.f['local_user1'], {}) + reply = client.post('/api/v1/groups/', self.f['local_user1'], {}) self.assertEqual(reply.status_code, 400) def test_list_groups(self): self.prepare_groups() - reply = client.get('/api/groups/', self.f['local_user1']) + reply = client.get('/api/v1/groups/', self.f['local_user1']) self.assertEqual(reply.status_code, 200) self.assertEqual(len(reply.json()), 1) - reply2 = client.get('/api/groups/', self.f['local_user2']) + reply2 = client.get('/api/v1/groups/', self.f['local_user2']) self.assertEqual(reply2.status_code, 200) self.assertEqual(len(reply2.json()), 0) def test_group_detail_member(self): self.prepare_groups() group = self.f['group1'] - reply = client.get('/api/groups/{}@{}/'.format(group.name, group.domain), self.f['local_user1']) + reply = client.get('/api/v1/groups/{}@{}/'.format(group.name, group.domain), self.f['local_user1']) self.assertEqual(reply.status_code, 200) self.assertEqual(reply.json()['handle'], str(group)) self.assertEqual(len(reply.json()['members']), 1) @@ -62,18 +62,18 @@ class GroupApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase): def test_group_detail_non_member(self): self.prepare_groups() group = self.f['group1'] - reply = client.get('/api/groups/{}@{}/'.format(group.name, group.domain), self.f['local_user2']) + reply = client.get('/api/v1/groups/{}@{}/'.format(group.name, group.domain), self.f['local_user2']) self.assertEqual(reply.status_code, 404) def test_group_detail_no_such_group(self): - reply = client.get('/api/groups/nonexistent@example.com/', self.f['local_user1']) + reply = client.get('/api/v1/groups/nonexistent@example.com/', self.f['local_user1']) self.assertEqual(reply.status_code, 404) def test_remove_member(self): self.prepare_groups() group = self.f['group1'] group.members.add(self.f['local_user2'].public_identity) - reply = client.delete('/api/groups/{}@{}/members/{}/'.format( + reply = client.delete('/api/v1/groups/{}@{}/members/{}/'.format( group.name, group.domain, self.f['local_user2'].public_identity.id), self.f['local_user1']) self.assertEqual(reply.status_code, 204) self.assertEqual(group.members.count(), 1) @@ -81,7 +81,7 @@ class GroupApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase): def test_remove_last_member_blocked(self): self.prepare_groups() group = self.f['group1'] - reply = client.delete('/api/groups/{}@{}/members/{}/'.format( + reply = client.delete('/api/v1/groups/{}@{}/members/{}/'.format( group.name, group.domain, self.f['local_user1'].public_identity.id), self.f['local_user1']) self.assertEqual(reply.status_code, 400) self.assertEqual(group.members.count(), 1) @@ -90,7 +90,7 @@ class GroupApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase): self.prepare_groups() group = self.f['group1'] group.members.add(self.f['local_user2'].public_identity) - reply = client.delete('/api/groups/{}@{}/members/{}/'.format( + reply = client.delete('/api/v1/groups/{}@{}/members/{}/'.format( group.name, group.domain, self.f['local_user1'].public_identity.id), self.f['ext_user1']) self.assertEqual(reply.status_code, 404) self.assertEqual(group.members.count(), 2) @@ -107,13 +107,13 @@ class GroupInviteApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase): inviter = self.f['local_user1'] invitee = self.f['local_user2'] - reply1 = client.post('/api/groups/{}@{}/invites/'.format(group.name, group.domain), inviter, + reply1 = client.post('/api/v1/groups/{}@{}/invites/'.format(group.name, group.domain), inviter, {'invitee': str(invitee)}) self.assertEqual(reply1.status_code, 201) secret = reply1.json()['secret'] self.assertEqual(GroupInvite.objects.count(), 1) - reply2 = client.post('/api/groupinvites/', inviter, { + reply2 = client.post('/api/v1/groupinvites/', inviter, { 'group': str(group), 'inviter': str(inviter), 'inviter_key': inviter.public_key(), 'invitee': str(invitee), 'secret': secret }) @@ -121,20 +121,20 @@ class GroupInviteApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase): incoming = GroupInviteIncoming.objects.get() self.assertEqual(incoming.invitee_user, invitee) - reply3 = client.get('/api/groupinvites/', invitee) + reply3 = client.get('/api/v1/groupinvites/', invitee) self.assertEqual(reply3.status_code, 200) self.assertEqual(len(reply3.json()), 1) self.assertEqual(reply3.json()[0]['group'], str(group)) self.assertEqual(reply3.json()[0]['inviter'], str(inviter)) - reply4 = client.post('/api/group_invites/accept/', invitee, { + reply4 = client.post('/api/v1/group_invites/accept/', invitee, { 'group': str(group), 'invitee': str(invitee), 'invitee_key': invitee.public_key(), 'secret': secret }) self.assertEqual(reply4.status_code, 201) self.assertTrue(group.is_member(invitee.public_identity)) self.assertEqual(GroupInvite.objects.count(), 0) - reply5 = client.post('/api/groupinvites/{}/accept/'.format(incoming.id), invitee) + reply5 = client.post('/api/v1/groupinvites/{}/accept/'.format(incoming.id), invitee) self.assertEqual(reply5.status_code, 201) self.assertEqual(reply5.json()['handle'], str(group)) self.assertEqual(GroupInviteIncoming.objects.count(), 0) @@ -144,7 +144,7 @@ class GroupInviteApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase): def test_invite_non_member_denied(self): group = self.f['group1'] - reply = client.post('/api/groups/{}@{}/invites/'.format(group.name, group.domain), self.f['local_user2'], + reply = client.post('/api/v1/groups/{}@{}/invites/'.format(group.name, group.domain), self.f['local_user2'], {'invitee': str(self.f['ext_user1'])}) self.assertEqual(reply.status_code, 404) self.assertEqual(GroupInvite.objects.count(), 0) @@ -152,7 +152,7 @@ class GroupInviteApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase): def test_invite_already_member(self): group = self.f['group1'] group.members.add(self.f['local_user2'].public_identity) - reply = client.post('/api/groups/{}@{}/invites/'.format(group.name, group.domain), self.f['local_user1'], + reply = client.post('/api/v1/groups/{}@{}/invites/'.format(group.name, group.domain), self.f['local_user1'], {'invitee': str(self.f['local_user2'])}) self.assertEqual(reply.status_code, 208) @@ -161,7 +161,7 @@ class GroupInviteApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase): group_name=self.f['group1'].name, group_domain=self.f['group1'].domain, inviter_username=self.f['local_user1'].username, inviter_domain=self.f['local_user1'].domain, invitee_user=self.f['local_user2'], secret='some-secret') - reply = client.delete('/api/groupinvites/{}/'.format(invite.id), self.f['local_user2']) + reply = client.delete('/api/v1/groupinvites/{}/'.format(invite.id), self.f['local_user2']) self.assertEqual(reply.status_code, 204) self.assertEqual(GroupInviteIncoming.objects.count(), 0) @@ -169,8 +169,8 @@ class GroupInviteApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase): group = self.f['group1'] inviter = self.f['local_user1'] invitee = self.f['local_user2'] - client.post('/api/groups/{}@{}/invites/'.format(group.name, group.domain), inviter, {'invitee': str(invitee)}) - reply = client.post('/api/group_invites/accept/', invitee, { + client.post('/api/v1/groups/{}@{}/invites/'.format(group.name, group.domain), inviter, {'invitee': str(invitee)}) + reply = client.post('/api/v1/group_invites/accept/', invitee, { 'group': str(group), 'invitee': str(invitee), 'invitee_key': invitee.public_key(), 'secret': 'not-the-right-secret' }) @@ -181,11 +181,11 @@ class GroupInviteApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase): group = self.f['group1'] inviter = self.f['local_user1'] invitee = DummyExternalUser('newmember', 'remote.example', known=False) - reply1 = client.post('/api/groups/{}@{}/invites/'.format(group.name, group.domain), inviter, + reply1 = client.post('/api/v1/groups/{}@{}/invites/'.format(group.name, group.domain), inviter, {'invitee': str(invitee)}) secret = reply1.json()['secret'] - reply = client.post('/api/group_invites/accept/', invitee, { + reply = client.post('/api/v1/group_invites/accept/', invitee, { 'group': str(group), 'invitee': str(invitee), 'invitee_key': invitee.public_key(), 'secret': secret }) self.assertEqual(reply.status_code, 201) @@ -199,11 +199,11 @@ class GroupInviteApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase): group = self.f['group1'] inviter = self.f['local_user1'] invitee = self.f['local_user2'] - reply1 = client.post('/api/groups/{}@{}/invites/'.format(group.name, group.domain), inviter, + reply1 = client.post('/api/v1/groups/{}@{}/invites/'.format(group.name, group.domain), inviter, {'invitee': str(invitee)}) secret = reply1.json()['secret'] bad_signature_client = SignatureAuthClient(bad_signature=True) - reply = bad_signature_client.post('/api/group_invites/accept/', invitee, { + reply = bad_signature_client.post('/api/v1/group_invites/accept/', invitee, { 'group': str(group), 'invitee': str(invitee), 'invitee_key': invitee.public_key(), 'secret': secret }) self.assertEqual(reply.status_code, 401) @@ -230,7 +230,7 @@ class GroupMembershipApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase inviter_username='someone', inviter_domain='other.example', invitee_user=invitee, secret='some-secret') - reply = client.post('/api/groupinvites/{}/accept/'.format(incoming.id), invitee) + reply = client.post('/api/v1/groupinvites/{}/accept/'.format(incoming.id), invitee) self.assertEqual(reply.status_code, 201) self.assertEqual(reply.json()['handle'], '#remoteworkshop@other.example') @@ -245,7 +245,7 @@ class GroupMembershipApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase inviter_username='someone', inviter_domain='other.example', invitee_user=self.f['local_user2'], secret='some-secret') - reply = client.post('/api/groupinvites/{}/accept/'.format(incoming.id), self.f['local_user1']) + reply = client.post('/api/v1/groupinvites/{}/accept/'.format(incoming.id), self.f['local_user1']) self.assertEqual(reply.status_code, 404) self.assertEqual(GroupMembership.objects.count(), 0) @@ -257,7 +257,7 @@ class GroupMembershipApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase inviter_username='someone', inviter_domain='other.example', invitee_user=self.f['local_user2'], secret='some-secret') - reply = client.delete('/api/groupinvites/{}/'.format(incoming.id), self.f['local_user2']) + reply = client.delete('/api/v1/groupinvites/{}/'.format(incoming.id), self.f['local_user2']) self.assertEqual(reply.status_code, 204) self.assertEqual(GroupMembership.objects.count(), 0) @@ -273,7 +273,7 @@ class GroupMembershipApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase inviter_username='someone', inviter_domain='other.example', invitee_user=invitee, secret='some-secret') - reply = client.post('/api/groupinvites/{}/accept/'.format(incoming.id), invitee) + reply = client.post('/api/v1/groupinvites/{}/accept/'.format(incoming.id), invitee) self.assertEqual(reply.status_code, 201) self.assertEqual(GroupMembership.objects.filter(user=invitee).count(), 1) @@ -283,12 +283,12 @@ class GroupMembershipApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase GroupMembership.objects.create( user=self.f['local_user1'], group_name='remoteworkshop', group_domain='other.example') - reply = client.get('/api/groupmemberships/', self.f['local_user1']) + reply = client.get('/api/v1/groupmemberships/', self.f['local_user1']) self.assertEqual(reply.status_code, 200) self.assertEqual(len(reply.json()), 1) self.assertEqual(reply.json()[0]['handle'], '#remoteworkshop@other.example') - reply2 = client.get('/api/groupmemberships/', self.f['local_user2']) + reply2 = client.get('/api/v1/groupmemberships/', self.f['local_user2']) self.assertEqual(reply2.status_code, 200) self.assertEqual(len(reply2.json()), 0) @@ -296,7 +296,7 @@ class GroupMembershipApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase GroupMembership.objects.create( user=self.f['local_user1'], group_name='remoteworkshop', group_domain='other.example') - reply = client.get('/api/groupmemberships/', self.f['ext_user1']) + reply = client.get('/api/v1/groupmemberships/', self.f['ext_user1']) # authenticate() returns bare None (not raise) for a caller with no local ToolshedUser, so # DRF falls through to IsAuthenticated denying an anonymous request -- 403, not 401 (same diff --git a/backend/toolshed/tests/test_idmap.py b/backend/toolshed/tests/test_idmap.py index bc54941..c73335a 100644 --- a/backend/toolshed/tests/test_idmap.py +++ b/backend/toolshed/tests/test_idmap.py @@ -13,40 +13,40 @@ class IdMapTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase): self.prepare_users() def test_idmap_includes_self(self): - reply = client.get('/api/idmap/', self.f['local_user1']) + reply = client.get('/api/v1/idmap/', self.f['local_user1']) self.assertEqual(reply.status_code, 200) identities = reply.json()['identities'] self.assertIn(str(self.f['local_user1']), [i['handle'] for i in identities]) def test_idmap_includes_friends(self): self.f['local_user1'].friends.add(self.f['local_user2'].public_identity) - reply = client.get('/api/idmap/', self.f['local_user1']) + reply = client.get('/api/v1/idmap/', self.f['local_user1']) self.assertEqual(reply.status_code, 200) identities = reply.json()['identities'] self.assertIn(str(self.f['local_user2']), [i['handle'] for i in identities]) def test_idmap_excludes_non_friends(self): - reply = client.get('/api/idmap/', self.f['local_user1']) + reply = client.get('/api/v1/idmap/', self.f['local_user1']) self.assertEqual(reply.status_code, 200) identities = reply.json()['identities'] self.assertNotIn(str(self.f['local_user2']), [i['handle'] for i in identities]) def test_idmap_includes_member_groups(self): self.prepare_groups() - reply = client.get('/api/idmap/', self.f['local_user1']) + reply = client.get('/api/v1/idmap/', self.f['local_user1']) self.assertEqual(reply.status_code, 200) groups = reply.json()['groups'] self.assertIn(str(self.f['group1']), [g['handle'] for g in groups]) def test_idmap_excludes_non_member_groups(self): self.prepare_groups() - reply = client.get('/api/idmap/', self.f['local_user2']) + reply = client.get('/api/v1/idmap/', self.f['local_user2']) self.assertEqual(reply.status_code, 200) groups = reply.json()['groups'] self.assertNotIn(str(self.f['group1']), [g['handle'] for g in groups]) def test_idmap_unauthenticated(self): - reply = Client().get('/api/idmap/') + reply = Client().get('/api/v1/idmap/') self.assertEqual(reply.status_code, 403) @@ -64,60 +64,60 @@ class ResolveShortIdApiTestCase(UserTestMixin, InventoryTestMixin, GroupTestMixi 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}/', + reply = client.get(f'/api/v1/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}/', + reply = client.get(f'/api/v1/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}/', + reply = client.get(f'/api/v1/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}/', + reply = client.get(f'/api/v1/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}/', + reply = client.get(f'/api/v1/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']) + reply = client.get('/api/v1/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']) + reply = client.get(f'/api/v1/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']) + reply = client.get(f'/api/v1/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}/', + reply = client.get(f'/api/v1/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/', + reply = client.get(f'/api/v1/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}/', + reply = client.get(f'/api/v1/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}) @@ -125,18 +125,18 @@ class ResolveShortIdApiTestCase(UserTestMixin, InventoryTestMixin, GroupTestMixi 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}/', + reply = client.get(f'/api/v1/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}/', + f'/api/v1/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']) + reply = client.get('/api/v1/resolve_short_id/group_item/999999/1/', self.f['local_user1']) self.assertEqual(reply.status_code, 404) diff --git a/backend/toolshed/tests/test_inventory.py b/backend/toolshed/tests/test_inventory.py index 7cc68d9..162d7c5 100644 --- a/backend/toolshed/tests/test_inventory.py +++ b/backend/toolshed/tests/test_inventory.py @@ -19,7 +19,7 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase): 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']) + reply = client.get('/api/v1/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') @@ -39,7 +39,7 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase): 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'], { + reply = client.post('/api/v1/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'], { 'availability_policy': 'rent', 'category': 'cat2', 'name': 'test3', @@ -62,7 +62,7 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase): 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'], { + reply = client.post('/api/v1/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'], { 'availability_policy': 'share', 'name': 'test3', 'description': 'test', @@ -81,7 +81,7 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase): 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'], { + reply = client.post('/api/v1/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'], { 'availability_policy': 'rent', 'owned_quantity': 1, 'image': '', @@ -90,7 +90,7 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase): 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'], { + reply = client.post('/api/v1/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'], { 'availability_policy': 'private', 'name': 'test3', 'description': 'test', @@ -110,7 +110,7 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase): 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'], { + reply = client.put('/api/v1/inventory_items/{}/1/'.format(self.own_handle), self.f['local_user1'], { 'availability_policy': 'sell', 'name': 'test4', 'description': 'new description', @@ -133,7 +133,7 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase): 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'], { + reply = client.patch('/api/v1/inventory_items/{}/1/'.format(self.own_handle), self.f['local_user1'], { 'description': 'new description2', 'category': 'cat1', 'owned_quantity': 100, @@ -153,7 +153,7 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase): 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'], { + reply = client.patch('/api/v1/inventory_items/{}/1/'.format(self.own_handle), self.f['local_user1'], { 'description': 'new description2', 'category': None, 'owned_quantity': 100, @@ -172,14 +172,14 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase): 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']) + reply = client.delete('/api/v1/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']) + reply = client.get('/api/v1/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') @@ -199,31 +199,31 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase): self.assertEqual(reply.json()[1]['availability_policy'], 'friends') def test_search_items2(self): - reply = client.get('/api/search/?query=test', self.f['local_user2']) + reply = client.get('/api/v1/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']) + reply = client.get('/api/v1/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']) + reply = client.get('/api/v1/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) + '/', + reply = client.get('/api/v1/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) + '/', + reply = client.get('/api/v1/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') @@ -232,31 +232,31 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase): # 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) + '/', + reply = client.get('/api/v1/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) + '/', + reply = client.get('/api/v1/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) + '/', + reply = client.get('/api/v1/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) + '/', + reply = client.get('/api/v1/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']) + reply = client.get('/api/v1/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']) + reply = client.get('/api/v1/inventory_items/testuser1/' + str(self.f['item1'].id) + '/', self.f['local_user2']) self.assertEqual(reply.status_code, 400) @@ -272,7 +272,7 @@ class TestInventoryItemWithFileApiTestCase(UserTestMixin, FilesTestMixin, Invent 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'], { + reply = client.post('/api/v1/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'], { 'name': 'test4', 'description': 'test', 'category': 'cat1', @@ -295,7 +295,7 @@ class TestInventoryItemWithFileApiTestCase(UserTestMixin, FilesTestMixin, Invent 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'], { + reply = client.post('/api/v1/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'], { 'name': 'test4', 'description': 'test', 'category': 'cat1', @@ -318,7 +318,7 @@ class TestInventoryItemWithFileApiTestCase(UserTestMixin, FilesTestMixin, Invent 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'], { + reply = client.post('/api/v1/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'], { 'name': 'test4', 'description': 'test', 'category': 'cat1', @@ -330,7 +330,7 @@ class TestInventoryItemWithFileApiTestCase(UserTestMixin, FilesTestMixin, Invent 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'], { + reply = client.post('/api/v1/inventory_items/{}/'.format(self.own_handle), self.f['local_user1'], { 'name': 'test4', 'description': 'test', 'category': 'cat1', @@ -357,7 +357,7 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest 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'], { + return client.post('/api/v1/inventory_items/{}/'.format(self.group_handle), self.f['local_user1'], { 'name': name, 'owned_quantity': 1, 'availability_policy': 'private', }) @@ -371,7 +371,7 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest 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'], { + reply = client.post('/api/v1/inventory_items/{}/'.format(self.group_handle), self.f['ext_user1'], { 'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private', }) self.assertEqual(reply.status_code, 403) @@ -379,7 +379,7 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest 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'], { + reply = client.patch('/api/v1/inventory_items/{}/{}/'.format(self.group_handle, item_id), self.f['local_user2'], { 'name': 'drill-renamed' }) self.assertEqual(reply.status_code, 200) @@ -387,20 +387,20 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest 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']) + reply = client.delete('/api/v1/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'], { + personal_reply = client.post('/api/v1/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']) + reply = client.delete('/api/v1/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) @@ -411,9 +411,9 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest # 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']) + reply = client.get('/api/v1/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'], { + reply = client.patch('/api/v1/inventory_items/{}/{}/'.format(self.group_handle, item_id), self.f['ext_user1'], { 'name': 'drill-renamed-by-remote-member' }) self.assertEqual(reply.status_code, 200) @@ -421,32 +421,32 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest 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']) + reply = client.get('/api/v1/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']) + reply = client.get('/api/v1/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']) + reply = client.get('/api/v1/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']) + reply = client.get('/api/v1/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'], { + reply = client.post('/api/v1/inventory_items/{}/'.format(self.group_handle), self.f['local_user1'], { 'availability_policy': 'rent', 'category': 'cat2', 'name': 'drill', @@ -468,21 +468,21 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest 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'], { + reply = client.post('/api/v1/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'], { + reply = client.post('/api/v1/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']) + reply = client.get('/api/v1/inventory_items/+nonexistent@example.com/', self.f['local_user1']) self.assertEqual(reply.status_code, 200) self.assertEqual(len(reply.json()), 0) @@ -490,7 +490,7 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest # 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'], { + reply = client.put('/api/v1/inventory_items/{}/{}/'.format(self.group_handle, item_id), self.f['local_user2'], { 'availability_policy': 'sell', 'name': 'drill-4000', 'description': 'new description', @@ -510,12 +510,12 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest 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'], { + reply = client.post('/api/v1/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'], { + reply = client.patch('/api/v1/inventory_items/{}/{}/'.format(self.group_handle, item_id), self.f['local_user2'], { 'category': None, 'tags': [], 'properties': [] }) self.assertEqual(reply.status_code, 200) @@ -524,7 +524,7 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest 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'], { + reply = client.post('/api/v1/inventory_items/{}/'.format(self.group_handle), self.f['local_user1'], { 'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private', 'storage_location': self.f['loc1'].id, }) @@ -534,7 +534,7 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest 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'], { + reply = client.post('/api/v1/inventory_items/{}/'.format(self.group_handle), self.f['local_user1'], { 'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private', 'files': [self.f['test_file1'].id], }) @@ -543,7 +543,7 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest 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'], { + reply = client.post('/api/v1/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'}], }) @@ -556,7 +556,7 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest # 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']) + reply = client.get('/api/v1/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']) diff --git a/backend/toolshed/tests/test_locations.py b/backend/toolshed/tests/test_locations.py index 1dc9163..c6d84b4 100644 --- a/backend/toolshed/tests/test_locations.py +++ b/backend/toolshed/tests/test_locations.py @@ -31,7 +31,7 @@ class LocationApiTestCase(UserTestMixin, InventoryTestMixin, LocationTestMixin, self.assertEqual(self.f['loc1'], self.f['loc4'].parent) def test_get_inventory(self): - reply = client.get('/api/inventory_items/{}/'.format(self.own_handle), self.f['local_user1']) + reply = client.get('/api/v1/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') @@ -51,7 +51,7 @@ class LocationApiTestCase(UserTestMixin, InventoryTestMixin, LocationTestMixin, self.assertEqual(reply.json()[1]['availability_policy'], 'friends') def test_get_inventory_item(self): - reply = client.get('/api/storage_locations/{}/'.format(self.own_handle), self.f['local_user1']) + reply = client.get('/api/v1/storage_locations/{}/'.format(self.own_handle), self.f['local_user1']) self.assertEqual(reply.status_code, 200) self.assertEqual(len(reply.json()), 4) self.assertEqual(reply.json()[0]['name'], 'loc1') @@ -72,7 +72,7 @@ class LocationApiTestCase(UserTestMixin, InventoryTestMixin, LocationTestMixin, self.assertEqual(reply.json()[3]['path'], 'loc1/loc4') def test_post_new_location(self): - reply = client.post('/api/storage_locations/{}/'.format(self.own_handle), self.f['local_user1'], { + reply = client.post('/api/v1/storage_locations/{}/'.format(self.own_handle), self.f['local_user1'], { 'name': 'loc5', 'description': 'a new location', }) @@ -85,7 +85,7 @@ class LocationApiTestCase(UserTestMixin, InventoryTestMixin, LocationTestMixin, self.assertEqual(reply.json()['path'], 'loc5') def test_post_new_nested_location(self): - reply = client.post('/api/storage_locations/{}/'.format(self.own_handle), self.f['local_user1'], { + reply = client.post('/api/v1/storage_locations/{}/'.format(self.own_handle), self.f['local_user1'], { 'name': 'loc5', 'parent': self.f['loc3'].id, }) @@ -96,7 +96,7 @@ class LocationApiTestCase(UserTestMixin, InventoryTestMixin, LocationTestMixin, def test_patch_location(self): reply = client.patch( - '/api/storage_locations/{}/{}/'.format(self.own_handle, self.f['loc2'].id), self.f['local_user1'], { + '/api/v1/storage_locations/{}/{}/'.format(self.own_handle, self.f['loc2'].id), self.f['local_user1'], { 'name': 'loc2-renamed', 'parent': self.f['loc1'].id, }) @@ -108,7 +108,7 @@ class LocationApiTestCase(UserTestMixin, InventoryTestMixin, LocationTestMixin, def test_delete_location(self): reply = client.delete( - '/api/storage_locations/{}/{}/'.format(self.own_handle, self.f['loc4'].id), self.f['local_user1']) + '/api/v1/storage_locations/{}/{}/'.format(self.own_handle, self.f['loc4'].id), self.f['local_user1']) self.assertEqual(reply.status_code, 204) self.assertEqual(StorageLocation.objects.count(), 3) self.assertEqual(StorageLocation.objects.filter(id=self.f['loc4'].id).count(), 0) @@ -117,14 +117,14 @@ class LocationApiTestCase(UserTestMixin, InventoryTestMixin, LocationTestMixin, item = InventoryItem.create_for_owner( owner=self.f['local_user1'], name='located_item', storage_location=self.f['loc3']) reply = client.delete( - '/api/storage_locations/{}/{}/'.format(self.own_handle, self.f['loc3'].id), self.f['local_user1']) + '/api/v1/storage_locations/{}/{}/'.format(self.own_handle, self.f['loc3'].id), self.f['local_user1']) self.assertEqual(reply.status_code, 204) item.refresh_from_db() self.assertIsNone(item.storage_location) self.assertEqual(InventoryItem.objects.filter(id=item.id).count(), 1) def test_locations_are_owner_scoped(self): - reply = client.get('/api/storage_locations/{}/'.format(str(self.f['local_user2'])), self.f['local_user2']) + reply = client.get('/api/v1/storage_locations/{}/'.format(str(self.f['local_user2'])), self.f['local_user2']) self.assertEqual(reply.status_code, 200) self.assertEqual(len(reply.json()), 0) @@ -133,7 +133,7 @@ class LocationApiTestCase(UserTestMixin, InventoryTestMixin, LocationTestMixin, # handle resolves and the location is visible - friends can read but never write, so this # is a 403 (found, not authorized), not a 404. reply = client.delete( - '/api/storage_locations/{}/{}/'.format(self.own_handle, self.f['loc1'].id), self.f['local_user2']) + '/api/v1/storage_locations/{}/{}/'.format(self.own_handle, self.f['loc1'].id), self.f['local_user2']) self.assertEqual(reply.status_code, 403) self.assertEqual(StorageLocation.objects.filter(id=self.f['loc1'].id).count(), 1) @@ -149,7 +149,7 @@ class GroupOwnedLocationApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestC self.group_handle = '+' + str(self.f['group1'])[1:] def create_group_location(self, name='shelf'): - return client.post('/api/storage_locations/{}/'.format(self.group_handle), self.f['local_user1'], { + return client.post('/api/v1/storage_locations/{}/'.format(self.group_handle), self.f['local_user1'], { 'name': name, }) @@ -163,7 +163,7 @@ class GroupOwnedLocationApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestC self.assertIsNone(reply.json()['owner']) def test_create_group_owned_location_non_member_denied(self): - reply = client.post('/api/storage_locations/{}/'.format(self.group_handle), self.f['ext_user1'], { + reply = client.post('/api/v1/storage_locations/{}/'.format(self.group_handle), self.f['ext_user1'], { 'name': 'shelf', }) self.assertEqual(reply.status_code, 403) @@ -172,7 +172,7 @@ class GroupOwnedLocationApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestC def test_other_member_can_edit(self): location_id = self.create_group_location().json()['id'] reply = client.patch( - '/api/storage_locations/{}/{}/'.format(self.group_handle, location_id), self.f['local_user2'], { + '/api/v1/storage_locations/{}/{}/'.format(self.group_handle, location_id), self.f['local_user2'], { 'name': 'shelf-renamed' }) self.assertEqual(reply.status_code, 200) @@ -181,7 +181,7 @@ class GroupOwnedLocationApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestC def test_other_member_can_delete(self): location_id = self.create_group_location().json()['id'] reply = client.delete( - '/api/storage_locations/{}/{}/'.format(self.group_handle, location_id), self.f['local_user2']) + '/api/v1/storage_locations/{}/{}/'.format(self.group_handle, location_id), self.f['local_user2']) self.assertEqual(reply.status_code, 204) self.assertEqual(StorageLocation.objects.filter(id=location_id).count(), 0) @@ -191,14 +191,14 @@ class GroupOwnedLocationApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestC # land on the same id. Every route being scoped by an explicit owner_handle - never a # bare, unscoped id - means this can no longer raise MultipleObjectsReturned. own_handle = str(self.f['local_user2']) - personal_reply = client.post('/api/storage_locations/{}/'.format(own_handle), self.f['local_user2'], + personal_reply = client.post('/api/v1/storage_locations/{}/'.format(own_handle), self.f['local_user2'], {'name': 'personal-shelf'}) self.assertEqual(personal_reply.json()['id'], 1) location_id = self.create_group_location().json()['id'] self.assertEqual(location_id, 1) reply = client.delete( - '/api/storage_locations/{}/{}/'.format(self.group_handle, location_id), self.f['local_user2']) + '/api/v1/storage_locations/{}/{}/'.format(self.group_handle, location_id), self.f['local_user2']) self.assertEqual(reply.status_code, 204) self.assertEqual(StorageLocation.objects.filter(name='shelf').count(), 0) @@ -210,10 +210,10 @@ class GroupOwnedLocationApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestC self.f['group1'].members.add(self.f['ext_user1'].public_identity) location_id = self.create_group_location().json()['id'] reply = client.get( - '/api/storage_locations/{}/{}/'.format(self.group_handle, location_id), self.f['ext_user1']) + '/api/v1/storage_locations/{}/{}/'.format(self.group_handle, location_id), self.f['ext_user1']) self.assertEqual(reply.status_code, 200) reply = client.patch( - '/api/storage_locations/{}/{}/'.format(self.group_handle, location_id), self.f['ext_user1'], { + '/api/v1/storage_locations/{}/{}/'.format(self.group_handle, location_id), self.f['ext_user1'], { 'name': 'shelf-renamed-by-remote-member' }) self.assertEqual(reply.status_code, 200) @@ -222,31 +222,31 @@ class GroupOwnedLocationApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestC def test_non_member_cannot_see_or_edit(self): location_id = self.create_group_location().json()['id'] reply = client.get( - '/api/storage_locations/{}/{}/'.format(self.group_handle, location_id), self.f['ext_user1']) + '/api/v1/storage_locations/{}/{}/'.format(self.group_handle, location_id), self.f['ext_user1']) self.assertEqual(reply.status_code, 404) def test_group_locations_excluded_from_personal_list(self): self.create_group_location() - reply = client.get('/api/storage_locations/{}/'.format(str(self.f['local_user1'])), self.f['local_user1']) + reply = client.get('/api/v1/storage_locations/{}/'.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_locations_listed_by_owner_handle(self): self.create_group_location() - reply = client.get('/api/storage_locations/{}/'.format(self.group_handle), self.f['local_user2']) + reply = client.get('/api/v1/storage_locations/{}/'.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'], 'shelf') def test_group_locations_not_listed_for_non_member(self): self.create_group_location() - reply = client.get('/api/storage_locations/{}/'.format(self.group_handle), self.f['ext_user1']) + reply = client.get('/api/v1/storage_locations/{}/'.format(self.group_handle), self.f['ext_user1']) self.assertEqual(reply.status_code, 200) self.assertEqual(len(reply.json()), 0) def test_group_location_can_be_parent_for_group_member(self): parent_id = self.create_group_location('shelf').json()['id'] - reply = client.post('/api/storage_locations/{}/'.format(self.group_handle), self.f['local_user2'], { + reply = client.post('/api/v1/storage_locations/{}/'.format(self.group_handle), self.f['local_user2'], { 'name': 'bin', 'parent': parent_id, }) self.assertEqual(reply.status_code, 201) @@ -259,7 +259,7 @@ class GroupOwnedLocationApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestC # InventoryItem.storage_location already had before group ownership existed here; not # something this feature narrows. personal = StorageLocation.create_for_owner(name='mine', owner=self.f['local_user1']) - reply = client.post('/api/storage_locations/{}/'.format(self.group_handle), self.f['local_user1'], { + reply = client.post('/api/v1/storage_locations/{}/'.format(self.group_handle), self.f['local_user1'], { 'name': 'bin', 'parent': personal.id, }) self.assertEqual(reply.status_code, 201) @@ -267,7 +267,7 @@ class GroupOwnedLocationApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestC def test_group_location_not_valid_parent_for_non_member(self): parent_id = self.create_group_location('shelf').json()['id'] reply = client.post( - '/api/storage_locations/{}/'.format(str(self.f['ext_user1'])), self.f['ext_user1'], { + '/api/v1/storage_locations/{}/'.format(str(self.f['ext_user1'])), self.f['ext_user1'], { 'name': 'bin', 'parent': parent_id, }) self.assertEqual(reply.status_code, 400) diff --git a/backend/toolshed/tests/test_offlinedata.py b/backend/toolshed/tests/test_offlinedata.py index ac5b55f..8a5a732 100644 --- a/backend/toolshed/tests/test_offlinedata.py +++ b/backend/toolshed/tests/test_offlinedata.py @@ -47,11 +47,11 @@ class _DeleteTestDataMixin(UserTestMixin, CategoryTestMixin, LocationTestMixin): class DeleteDataTestCase(_DeleteTestDataMixin, ToolshedTestCase): def test_delete_data_anonymous(self): - response = anonymous_client.delete('/api/account_data/') + response = anonymous_client.delete('/api/v1/account_data/') self.assertEqual(response.status_code, 403) def test_delete_data_removes_all_owned_data_but_keeps_account(self): - response = client.delete('/api/account_data/', self.f['local_user1']) + response = client.delete('/api/v1/account_data/', self.f['local_user1']) self.assertEqual(response.status_code, 200) summary = response.json() @@ -83,14 +83,14 @@ class DeleteDataTestCase(_DeleteTestDataMixin, ToolshedTestCase): class DeleteAccountTestCase(_DeleteTestDataMixin, ToolshedTestCase): def test_delete_account_anonymous(self): - response = anonymous_client.delete('/api/account/') + response = anonymous_client.delete('/api/v1/account/') self.assertEqual(response.status_code, 403) def test_delete_account_removes_data_and_closes_account(self): user1_id = self.f['local_user1'].id identity_id = self.f['local_user1'].public_identity_id - response = client.delete('/api/account/', self.f['local_user1']) + response = client.delete('/api/v1/account/', self.f['local_user1']) self.assertEqual(response.status_code, 200) summary = response.json() @@ -219,7 +219,7 @@ class ImportInventoryPropertiesTestCase(UserTestMixin, CategoryTestMixin, TagTes class ExportImportApiRoundTripTestCase(UserTestMixin, CategoryTestMixin, TagTestMixin, PropertyTestMixin, ToolshedTestCase): - """End-to-end coverage of the /api/export/ + /api/import/ endpoints (as actually used by clients), rather than calling the internal helper functions directly - this is what a real export/import round trip between two accounts looks like.""" + """End-to-end coverage of the /api/v1/export/ + /api/v1/import/ endpoints (as actually used by clients), rather than calling the internal helper functions directly - this is what a real export/import round trip between two accounts looks like.""" def setUp(self): super().setUp() @@ -238,11 +238,11 @@ class ExportImportApiRoundTripTestCase(UserTestMixin, CategoryTestMixin, TagTest 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') - export_reply = client.get('/api/export/', self.f['local_user1']) + export_reply = client.get('/api/v1/export/', self.f['local_user1']) self.assertEqual(export_reply.status_code, 200) zip_bytes = export_reply.content - import_reply = client.post('/api/import/', self.f['local_user2'], + import_reply = client.post('/api/v1/import/', self.f['local_user2'], {'zip': base64.b64encode(zip_bytes).decode('ascii')}) self.assertEqual(import_reply.status_code, 200) summary = import_reply.json() diff --git a/backend/toolshed/tests/test_property.py b/backend/toolshed/tests/test_property.py index 1090fc3..eb5b2ea 100644 --- a/backend/toolshed/tests/test_property.py +++ b/backend/toolshed/tests/test_property.py @@ -41,7 +41,7 @@ class PropertyApiTestCase(PropertyTestMixin, CategoryTestMixin, UserTestMixin, T self.prepare_properties() def test_get_properties(self): - reply = client.get('/api/properties/', self.f['local_user1']) + reply = client.get('/api/v1/properties/', self.f['local_user1']) self.assertEqual(reply.status_code, 200) self.assertEqual(len(reply.json()), 3) self.assertEqual(reply.json()[0]['name'], 'prop1') diff --git a/backend/toolshed/tests/test_tag.py b/backend/toolshed/tests/test_tag.py index 7a32d36..228d242 100644 --- a/backend/toolshed/tests/test_tag.py +++ b/backend/toolshed/tests/test_tag.py @@ -41,7 +41,7 @@ class TagApiTestCase(TagTestMixin, CategoryTestMixin, UserTestMixin, ToolshedTes self.prepare_tags() def test_get_tags(self): - reply = client.get('/api/tags/', self.f['local_user1']) + reply = client.get('/api/v1/tags/', self.f['local_user1']) self.assertEqual(reply.status_code, 200) self.assertEqual(len(reply.json()), 3) self.assertEqual(reply.json()[0], 'tag1') diff --git a/backend/toolshed/tests/test_workflow_api.py b/backend/toolshed/tests/test_workflow_api.py index 98489df..5388679 100644 --- a/backend/toolshed/tests/test_workflow_api.py +++ b/backend/toolshed/tests/test_workflow_api.py @@ -20,7 +20,7 @@ class WorkflowInstanceApiTestCase(UserTestMixin, WorkflowTestMixin, ToolshedTest def test_get_workflow_instances(self): - reply = client.get('/api/workflows/', self.f['local_user1']) + reply = client.get('/api/v1/workflows/', self.f['local_user1']) self.assertEqual(reply.status_code, status.HTTP_200_OK) self.assertEqual(len(reply.data), 3) self.assertEqual(reply.data[0]['name'], 'workflow1') @@ -39,7 +39,7 @@ class WorkflowInstanceApiTestCase(UserTestMixin, WorkflowTestMixin, ToolshedTest def test_get_workflow_instances_user2(self): - reply = client.get('/api/workflows/', self.f['local_user2']) + reply = client.get('/api/v1/workflows/', self.f['local_user2']) self.assertEqual(reply.status_code, status.HTTP_200_OK) self.assertEqual(len(reply.data), 1) self.assertEqual(reply.data[0]['name'], 'workflow2') diff --git a/cli-client/toolshed-client.py b/cli-client/toolshed-client.py index cdbeffe..6d4a216 100755 --- a/cli-client/toolshed-client.py +++ b/cli-client/toolshed-client.py @@ -8,9 +8,9 @@ from nacl.signing import SigningKey from json import dumps, loads COMMANDS = { - 'getinventory': {'path': '/api/inventory_items/{handle}/', 'method': 'get'}, - 'additem': {'path': '/api/inventory_items/{handle}/', 'method': 'post'}, - 'delitem': {'path': '/api/inventory_items/{handle}/{internal_id}/', 'method': 'delete'}, + 'getinventory': {'path': '/api/v1/inventory_items/{handle}/', 'method': 'get'}, + 'additem': {'path': '/api/v1/inventory_items/{handle}/', 'method': 'post'}, + 'delitem': {'path': '/api/v1/inventory_items/{handle}/{internal_id}/', 'method': 'delete'}, } @@ -110,7 +110,7 @@ class ToolshedApi: def get_raw(self, target): """Like get(), but returns the raw response body instead of parsing it as JSON - for - endpoints like /api/export/ that hand back a zip file, not a JSON document.""" + endpoints like /api/v1/export/ that hand back a zip file, not a JSON document.""" response = self._send('GET', target) if not response.ok: raise ApiError("{} {} from {}: {}".format( @@ -199,7 +199,7 @@ def build_request(api, spec, command, cmd_args, json_input): def run_command(api, cmd, cmd_args, json_input): if cmd == 'export': path = cmd_args[0] if cmd_args else 'toolshed-export.zip' - data = api.get_raw("/api/export/") + data = api.get_raw("/api/v1/export/") with open(path, 'wb') as f: f.write(data) return {'exported_to': path, 'bytes': len(data)} @@ -209,7 +209,7 @@ def run_command(api, cmd, cmd_args, json_input): path = cmd_args[0] with open(path, 'rb') as f: data = f.read() - return api.post("/api/import/", {"zip": base64.b64encode(data).decode('ascii')}) + return api.post("/api/v1/import/", {"zip": base64.b64encode(data).decode('ascii')}) command = COMMANDS.get(cmd) if command is None: diff --git a/frontend/src/components/workflow/workflows/BulkItemImportWorkflow.vue b/frontend/src/components/workflow/workflows/BulkItemImportWorkflow.vue index 07ecec1..a5bd6f3 100644 --- a/frontend/src/components/workflow/workflows/BulkItemImportWorkflow.vue +++ b/frontend/src/components/workflow/workflows/BulkItemImportWorkflow.vue @@ -579,7 +579,6 @@ export default { this.importError = null; const created_item_ids = []; const errors = []; - // Each row is created via the same generic POST /api/inventory_items/ endpoint any client would use. for (const [index, row] of this.items.entries()) { if (!row.name) { errors.push(`Row ${index + 1}: missing required "name" field, skipped.`); diff --git a/frontend/src/store.js b/frontend/src/store.js index 6c6b85b..483072c 100644 --- a/frontend/src/store.js +++ b/frontend/src/store.js @@ -395,7 +395,7 @@ export default createStore({ }, async fetchInventoryItems({state, commit, dispatch, getters}) { const servers = await dispatch('getHomeServers') - const items = await servers.get(getters.signAuth, '/api/inventory_items/' + state.user + '/') + const items = await servers.get(getters.signAuth, '/api/v1/inventory_items/' + state.user + '/') items.map(item => item.files.map(file => file.owner = item.owner)) commit('setInventoryItems', {url: '/', items}) return items @@ -407,7 +407,7 @@ export default createStore({ const owner = item.owner_group ? encodeHandleForUrl(item.owner_group) : state.user const data = {availability_policy: 'private', ...item} delete data.owner_group - const reply = await servers.post(getters.signAuth, '/api/inventory_items/' + owner + '/', data) + const reply = await servers.post(getters.signAuth, '/api/v1/inventory_items/' + owner + '/', data) state.last_load.files = 0 return reply }, @@ -418,28 +418,28 @@ export default createStore({ const data = {availability_policy: 'friends', ...item} data.files = data.files.map(file => file.id) // Path is scoped by the owner's handle, not the item's own id domain - see docs/implementation.md#owner-handle-scoped-routes. - const path = '/api/inventory_items/' + encodeHandleForUrl(item.owner_group || item.owner) + '/' + item.id + '/' + const path = '/api/v1/inventory_items/' + encodeHandleForUrl(item.owner_group || item.owner) + '/' + item.id + '/' return await servers.patch(getters.signAuth, path, data) }, async deleteInventoryItem({state, dispatch, getters}, item) { const servers = item.owner_group ? await dispatch('getFriendServers', {username: 'x@' + splitGroupHandle(item.owner_group).domain}) : await dispatch('getHomeServers') - const path = '/api/inventory_items/' + encodeHandleForUrl(item.owner_group || item.owner) + '/' + item.id + '/' + const path = '/api/v1/inventory_items/' + encodeHandleForUrl(item.owner_group || item.owner) + '/' + item.id + '/' const ret = await servers.delete(getters.signAuth, path) dispatch('fetchInventoryItems') return ret }, async fetchSearchResults({state, commit, dispatch, getters}, {query}) { const servers = await dispatch('getAllKnownServers') - const items = await servers.get(getters.signAuth, '/api/search/?query=' + query) + const items = await servers.get(getters.signAuth, '/api/v1/search/?query=' + query) items.map(item => item.files.map(file => file.owner = item.owner)) commit('setInventoryItems', {url: '/search/' + query, items}) return items }, async fetchFriends({commit, dispatch, getters, state}) { const servers = await dispatch('getHomeServers') - const data = await servers.get(getters.signAuth, '/api/friends/') + const data = await servers.get(getters.signAuth, '/api/v1/friends/') commit('setFriends', data) return data }, @@ -461,7 +461,7 @@ export default createStore({ try { const servers = await dispatch('getFriendServers', {username: owner}); // owner is a full handle, looked up by owner not requester. See docs/implementation.md#owner-handle-scoped-routes. - const item = await servers.get(getters.signAuth, '/api/inventory_items/' + owner + '/' + id + '/'); + const item = await servers.get(getters.signAuth, '/api/v1/inventory_items/' + owner + '/' + id + '/'); if (item && item.files) { item.files.forEach(file => file.owner = item.owner) } @@ -485,14 +485,14 @@ export default createStore({ }, async fetchFriendRequests({state, dispatch, getters}) { const servers = await dispatch('getHomeServers') - return await servers.get(getters.signAuth, '/api/friendrequests/') + return await servers.get(getters.signAuth, '/api/v1/friendrequests/') }, async requestFriend({state, dispatch, getters}, {username}) { if (username in state.friends) { return true; } const home_servers = await dispatch('getHomeServers') - const home_reply = await home_servers.post(getters.signAuth, '/api/friendrequests/', { + const home_reply = await home_servers.post(getters.signAuth, '/api/v1/friendrequests/', { befriender: state.user, befriendee: username }) @@ -500,7 +500,7 @@ export default createStore({ return false; const befriendee_servers = await dispatch('getFriendServers', {username}) - const ext_reply = await befriendee_servers.post(getters.signAuth, '/api/friendrequests/', { + const ext_reply = await befriendee_servers.post(getters.signAuth, '/api/v1/friendrequests/', { befriender: state.user, befriendee: username, befriender_key: nacl.to_hex(state.keypair.signPk), @@ -510,11 +510,11 @@ export default createStore({ }, async acceptFriend({state, dispatch, getters}, {id, secret, befriender}) { const home_servers = await dispatch('getHomeServers') - const home_reply = await home_servers.post(getters.signAuth, '/api/friends/', { + const home_reply = await home_servers.post(getters.signAuth, '/api/v1/friends/', { friend_request_id: id, secret: secret }) const ext_servers = await dispatch('getFriendServers', {username: befriender}) - const ext_reply = await ext_servers.post(getters.signAuth, '/api/friendrequests/', { + const ext_reply = await ext_servers.post(getters.signAuth, '/api/v1/friendrequests/', { befriender: state.user, befriendee: befriender, befriender_key: nacl.to_hex(state.keypair.signPk), @@ -524,21 +524,21 @@ export default createStore({ }, async declineFriend({state, dispatch, getters}, {id}) { const servers = await dispatch('getHomeServers') - return await servers.delete(getters.signAuth, '/api/friendrequests/' + id + '/') + return await servers.delete(getters.signAuth, '/api/v1/friendrequests/' + id + '/') }, async dropFriend({state, dispatch, getters}, {id}) { const servers = await dispatch('getHomeServers') - return await servers.delete(getters.signAuth, '/api/friends/' + id + '/') + return await servers.delete(getters.signAuth, '/api/v1/friends/' + id + '/') }, async fetchGroups({commit, dispatch, getters}) { const servers = await dispatch('getHomeServers') - const data = await servers.get(getters.signAuth, '/api/groups/') + const data = await servers.get(getters.signAuth, '/api/v1/groups/') commit('setGroups', data) return data }, async fetchIdMap({commit, dispatch, getters}) { const servers = await dispatch('getHomeServers') - const idmap = await servers.get(getters.signAuth, '/api/idmap/') + const idmap = await servers.get(getters.signAuth, '/api/v1/idmap/') commit('setIdMap', idmap) return idmap }, @@ -546,7 +546,7 @@ export default createStore({ async resolveShortId({dispatch, getters}, {domain, kind, ownerId, localId}) { try { const servers = await dispatch('getFriendServers', {username: 'x@' + domain}) - return await servers.get(getters.signAuth, `/api/resolve_short_id/${kind}/${ownerId}/${localId}/`) + return await servers.get(getters.signAuth, `/api/v1/resolve_short_id/${kind}/${ownerId}/${localId}/`) } catch (error) { console.error(`Failed to resolve short id (${kind}, ${ownerId}, ${localId}) on ${domain}:`, error); return null; @@ -556,27 +556,27 @@ export default createStore({ async fetchGroup({dispatch, getters}, {handle}) { const {domain} = splitGroupHandle(handle) const servers = await dispatch('getFriendServers', {username: 'x@' + domain}) - return await servers.get(getters.signAuth, `/api/groups/${handle.slice(1)}/`) + return await servers.get(getters.signAuth, `/api/v1/groups/${handle.slice(1)}/`) }, async createGroup({dispatch, getters}, {name}) { const servers = await dispatch('getHomeServers') - return await servers.post(getters.signAuth, '/api/groups/', {name}) + return await servers.post(getters.signAuth, '/api/v1/groups/', {name}) }, async removeGroupMember({dispatch, getters}, {groupHandle, identityId}) { const {domain} = splitGroupHandle(groupHandle) const servers = await dispatch('getFriendServers', {username: 'x@' + domain}) return await servers.delete( - getters.signAuth, '/api/groups/' + groupHandle.slice(1) + '/members/' + identityId + '/') + getters.signAuth, '/api/v1/groups/' + groupHandle.slice(1) + '/members/' + identityId + '/') }, async fetchGroupInvites({commit, dispatch, getters}) { const servers = await dispatch('getHomeServers') - const data = await servers.get(getters.signAuth, '/api/groupinvites/') + const data = await servers.get(getters.signAuth, '/api/v1/groupinvites/') commit('setGroupInvites', data) return data }, async fetchGroupMemberships({commit, dispatch, getters}) { const servers = await dispatch('getHomeServers') - const data = await servers.get(getters.signAuth, '/api/groupmemberships/') + const data = await servers.get(getters.signAuth, '/api/v1/groupmemberships/') commit('setGroupMemberships', data) return data }, @@ -584,12 +584,12 @@ export default createStore({ const {domain} = splitGroupHandle(groupHandle) const group_servers = await dispatch('getFriendServers', {username: 'x@' + domain}) const group_reply = await group_servers.post( - getters.signAuth, '/api/groups/' + groupHandle.slice(1) + '/invites/', {invitee}) + getters.signAuth, '/api/v1/groups/' + groupHandle.slice(1) + '/invites/', {invitee}) if (!group_reply.secret) { return false } const invitee_servers = await dispatch('getFriendServers', {username: invitee}) - await invitee_servers.post(getters.signAuth, '/api/groupinvites/', { + await invitee_servers.post(getters.signAuth, '/api/v1/groupinvites/', { group: groupHandle, inviter: state.user, inviter_key: nacl.to_hex(state.keypair.signPk), @@ -601,24 +601,24 @@ export default createStore({ async acceptGroupInvite({state, dispatch, getters}, invite) { const group_domain = invite.group.split('@')[1] const group_servers = await dispatch('getFriendServers', {username: 'x@' + group_domain}) - await group_servers.post(getters.signAuth, '/api/group_invites/accept/', { + await group_servers.post(getters.signAuth, '/api/v1/group_invites/accept/', { group: invite.group, invitee: state.user, invitee_key: nacl.to_hex(state.keypair.signPk), secret: invite.secret }) const home_servers = await dispatch('getHomeServers') - await home_servers.post(getters.signAuth, '/api/groupinvites/' + invite.id + '/accept/', {}) + await home_servers.post(getters.signAuth, '/api/v1/groupinvites/' + invite.id + '/accept/', {}) return true }, async declineGroupInvite({dispatch, getters}, invite) { const servers = await dispatch('getHomeServers') - return await servers.delete(getters.signAuth, '/api/groupinvites/' + invite.id + '/') + return await servers.delete(getters.signAuth, '/api/v1/groupinvites/' + invite.id + '/') }, async fetchGroupInventoryItems({commit, dispatch, getters}, {groupHandle}) { const {domain} = splitGroupHandle(groupHandle) const servers = await dispatch('getFriendServers', {username: 'x@' + domain}) - const items = await servers.get(getters.signAuth, '/api/inventory_items/' + encodeHandleForUrl(groupHandle) + '/') + const items = await servers.get(getters.signAuth, '/api/v1/inventory_items/' + encodeHandleForUrl(groupHandle) + '/') // Keyed by the full handle, not a bare id: a group pk is only unique within its own // backend's database, so two different domains could otherwise collide on the same // item_map key - the handle already carries the domain, so no separate namespacing is needed. @@ -630,7 +630,7 @@ export default createStore({ return state.files } const servers = await dispatch('getHomeServers') - const data = await servers.get(getters.signAuth, '/api/files/') + const data = await servers.get(getters.signAuth, '/api/v1/files/') data.map(file => file.owner = state.user) commit('setFiles', data) state.last_load.files = Date.now() @@ -638,11 +638,11 @@ export default createStore({ }, async fetchFilesByItem({state, commit, dispatch, getters}, {id}) { const servers = await dispatch('getHomeServers') - return await servers.get(getters.signAuth, '/api/item_files/' + id + '/') + return await servers.get(getters.signAuth, '/api/v1/item_files/' + id + '/') }, async pushFile({state, dispatch, getters}, {item_id, file}) { const servers = await dispatch('getHomeServers') - const data = await servers.post(getters.signAuth, '/api/item_files/' + item_id + '/', file) + const data = await servers.post(getters.signAuth, '/api/v1/item_files/' + item_id + '/', file) if (data.name) { data.owner = state.user state.files.push(data) @@ -651,28 +651,28 @@ export default createStore({ }, async deleteFile({state, dispatch, getters}, {id}) { const servers = await dispatch('getHomeServers') - await servers.delete(getters.signAuth, '/api/files/' + id + '/') + await servers.delete(getters.signAuth, '/api/v1/files/' + id + '/') state.files = state.files.filter(file => file.id !== id) }, async deleteItemFile({state, dispatch, getters}, {item_id, file_id}) { const servers = await dispatch('getHomeServers') - await servers.delete(getters.signAuth, '/api/item_files/' + item_id + '/' + file_id + '/') + await servers.delete(getters.signAuth, '/api/v1/item_files/' + item_id + '/' + file_id + '/') state.files = state.files.filter(file => file.id !== file_id) }, async stageFile({state, dispatch, getters}, {lifetime_id, file}) { const servers = await dispatch('getHomeServers') - const data = await servers.post(getters.signAuth, '/api/staged_files/' + lifetime_id + '/', file) + const data = await servers.post(getters.signAuth, '/api/v1/staged_files/' + lifetime_id + '/', file) if (data.hash) { return data.hash } }, async unstageFile({state, dispatch, getters}, {lifetime_id, file_hash}) { const servers = await dispatch('getHomeServers') - await servers.delete(getters.signAuth, '/api/staged_files/' + lifetime_id + '/' + file_hash + '/') + await servers.delete(getters.signAuth, '/api/v1/staged_files/' + lifetime_id + '/' + file_hash + '/') }, async commitStagedFile({state, dispatch, getters}, {item_id, file_hash}) { const servers = await dispatch('getHomeServers') - const data = await servers.post(getters.signAuth, '/api/item_files/' + item_id + '/', {file_hash}) + const data = await servers.post(getters.signAuth, '/api/v1/item_files/' + item_id + '/', {file_hash}) if (data.name) { data.owner = state.user state.files.push(data) @@ -684,7 +684,7 @@ export default createStore({ return state.tags } const servers = await dispatch('getHomeServers') - const data = await servers.get(getters.signAuth, '/api/tags/') + const data = await servers.get(getters.signAuth, '/api/v1/tags/') commit('setTags', data) state.last_load.tags = Date.now() return data @@ -694,7 +694,7 @@ export default createStore({ return state.properties } const servers = await dispatch('getHomeServers') - const data = await servers.get(getters.signAuth, '/api/properties/') + const data = await servers.get(getters.signAuth, '/api/v1/properties/') commit('setProperties', data) state.last_load.properties = Date.now() return data @@ -704,7 +704,7 @@ export default createStore({ return state.categories } const servers = await dispatch('getHomeServers') - const data = await servers.get(getters.signAuth, '/api/categories/') + const data = await servers.get(getters.signAuth, '/api/v1/categories/') commit('setCategories', data) state.last_load.categories = Date.now() return data @@ -714,7 +714,7 @@ export default createStore({ return state.availability_policies } const servers = await dispatch('getHomeServers') - const data = await servers.get(getters.signAuth, '/api/availability_policies/') + const data = await servers.get(getters.signAuth, '/api/v1/availability_policies/') commit('setAvailabilityPolicies', data.map(policy => ({slug: policy[0], text: policy[1]}))) state.last_load.availability_policies = Date.now() return data @@ -724,7 +724,7 @@ export default createStore({ return state.storage_locations } const servers = await dispatch('getHomeServers') - const data = await servers.get(getters.signAuth, '/api/storage_locations/' + state.user + '/') + const data = await servers.get(getters.signAuth, '/api/v1/storage_locations/' + state.user + '/') commit('setStorageLocations', data) state.last_load.storage_locations = Date.now() return data @@ -733,7 +733,7 @@ export default createStore({ const {domain} = splitGroupHandle(groupHandle) const servers = await dispatch('getFriendServers', {username: 'x@' + domain}) const locations = await servers.get( - getters.signAuth, '/api/storage_locations/' + encodeHandleForUrl(groupHandle) + '/') + getters.signAuth, '/api/v1/storage_locations/' + encodeHandleForUrl(groupHandle) + '/') commit('setLocationsForKey', {url: '/' + groupHandle, locations}) return locations }, @@ -755,7 +755,7 @@ export default createStore({ ? await dispatch('getFriendServers', {username: 'x@' + splitGroupHandle(location.owner_group).domain}) : await dispatch('getHomeServers') // Path is scoped by the owner's handle, not the location's own id domain - see docs/implementation.md#owner-handle-scoped-routes. - const path = '/api/storage_locations/' + encodeHandleForUrl(location.owner_group || location.owner) + '/' + location.id + '/' + const path = '/api/v1/storage_locations/' + encodeHandleForUrl(location.owner_group || location.owner) + '/' + location.id + '/' const ret = await servers.delete(getters.signAuth, path) state.last_load.storage_locations = 0 dispatch('fetchStorageLocations') @@ -771,7 +771,7 @@ export default createStore({ const data = {...location} delete data.owner_group if (data.parent === '') data.parent = null - const reply = await servers.post(getters.signAuth, '/api/storage_locations/' + owner + '/', data) + const reply = await servers.post(getters.signAuth, '/api/v1/storage_locations/' + owner + '/', data) state.last_load.storage_locations = 0 return reply }, @@ -781,7 +781,7 @@ export default createStore({ : await dispatch('getHomeServers') const data = {...location} if (data.parent === '') data.parent = null - const path = '/api/storage_locations/' + encodeHandleForUrl(location.owner_group || location.owner) + '/' + location.id + '/' + const path = '/api/v1/storage_locations/' + encodeHandleForUrl(location.owner_group || location.owner) + '/' + location.id + '/' const reply = await servers.patch(getters.signAuth, path, data) state.last_load.storage_locations = 0 dispatch('fetchStorageLocations') @@ -797,7 +797,7 @@ export default createStore({ return state.info } const servers = await dispatch('getHomeServers') - const data = await servers.get(getters.signAuth, '/api/info/') + const data = await servers.get(getters.signAuth, '/api/v1/info/') commit('setTags', data.tags) commit('setProperties', data.properties) commit('setCategories', data.categories) @@ -820,7 +820,7 @@ export default createStore({ return state.active_workflows } const servers = await dispatch('getHomeServers') - const data = await servers.get(getters.signAuth, '/api/workflows/') + const data = await servers.get(getters.signAuth, '/api/v1/workflows/') const workflows = data.map(deserializeWorkflowPayload) commit('setActiveWorkflows', workflows) state.last_load.active_workflows = Date.now() @@ -830,19 +830,19 @@ export default createStore({ const servers = await dispatch('getHomeServers') // The backend stores payload as an opaque string; the frontend (de)serializes it. // See docs/implementation.md#workflow-payload-is-an-opaque-string. - const data = await servers.post(getters.signAuth, '/api/workflows/', serializeWorkflowPayload(workflowData)) + const data = await servers.post(getters.signAuth, '/api/v1/workflows/', serializeWorkflowPayload(workflowData)) state.last_load.active_workflows = 0 // Invalidate cache return deserializeWorkflowPayload(data) }, async updateWorkflow({state, commit, dispatch, getters}, workflow) { const servers = await dispatch('getHomeServers') - const data = await servers.patch(getters.signAuth, '/api/workflows/' + workflow.id + '/', serializeWorkflowPayload(workflow)) + const data = await servers.patch(getters.signAuth, '/api/v1/workflows/' + workflow.id + '/', serializeWorkflowPayload(workflow)) state.last_load.active_workflows = 0 // Invalidate cache return deserializeWorkflowPayload(data) }, async updateWorkflowStep({state, commit, dispatch, getters}, {workflowId, currentStep, payload}) { const servers = await dispatch('getHomeServers') - const data = await servers.post(getters.signAuth, '/api/workflows/' + workflowId + '/update_step/', { + const data = await servers.post(getters.signAuth, '/api/v1/workflows/' + workflowId + '/update_step/', { current_step: currentStep, payload: JSON.stringify(payload) }) @@ -851,7 +851,7 @@ export default createStore({ }, async deleteWorkflow({state, commit, dispatch, getters}, workflowId) { const servers = await dispatch('getHomeServers') - await servers.delete(getters.signAuth, '/api/workflows/' + workflowId + '/') + await servers.delete(getters.signAuth, '/api/v1/workflows/' + workflowId + '/') state.last_load.active_workflows = 0 // Invalidate cache return true } diff --git a/frontend/src/views/Pairing.vue b/frontend/src/views/Pairing.vue index 21d6f9f..f57d2ce 100644 --- a/frontend/src/views/Pairing.vue +++ b/frontend/src/views/Pairing.vue @@ -152,7 +152,7 @@ export default { const unreachable = new NeighborsCache(); const servers = await this.lookupServer({username: this.username}).then(servers => new ServerSet(servers, unreachable)) const auth = createSignAuth(this.username, this.keypair.signSk); - const reply = await servers.getRaw(auth, '/api/friends/'); + const reply = await servers.getRaw(auth, '/api/v1/friends/'); if (reply.status === 200) { this.setUserFromIdentityRecord({username: this.username, keypair: this.keypair}); router.push({path: '/'}); diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue index 9a99b7c..4fa1274 100644 --- a/frontend/src/views/Settings.vue +++ b/frontend/src/views/Settings.vue @@ -69,7 +69,7 @@ export default { } try { const servers = await this.getHomeServers(); - const response = await servers.delete(this.signAuth, '/api/account/'); + const response = await servers.delete(this.signAuth, '/api/v1/account/'); if (!response || !response.ok) { const errorBody = response ? await response.json().catch(() => ({})) : {}; alert('Account deletion failed: ' + (errorBody.detail || response?.statusText || 'unknown error')); diff --git a/frontend/src/views/settings/Data.vue b/frontend/src/views/settings/Data.vue index c25f7d8..e42f328 100644 --- a/frontend/src/views/settings/Data.vue +++ b/frontend/src/views/settings/Data.vue @@ -102,7 +102,7 @@ export default { } try { const servers = await this.getHomeServers(); - const response = await servers.delete(this.signAuth, '/api/account_data/'); + const response = await servers.delete(this.signAuth, '/api/v1/account_data/'); if (!response || !response.ok) { const errorBody = response ? await response.json().catch(() => ({})) : {}; alert('Data deletion failed: ' + (errorBody.detail || response?.statusText || 'unknown error')); @@ -148,7 +148,7 @@ export default { try { const base64 = await this.fileToBase64(this.selectedFile); const servers = await this.getHomeServers(); - const summary = await servers.post(this.signAuth, '/api/import/', {zip: base64}); + const summary = await servers.post(this.signAuth, '/api/v1/import/', {zip: base64}); if (summary && summary.detail) { alert('Data import failed: ' + summary.detail); return; @@ -172,7 +172,7 @@ export default { }, async exportData() { const servers = await this.getHomeServers(); - const data = await servers.getRaw(this.signAuth, '/api/export/'); + const data = await servers.getRaw(this.signAuth, '/api/v1/export/'); const blob = new Blob([await data.blob()], {type: 'application/zip'}); const url = window.URL.createObjectURL(blob); const a = document.createElement('a');