This commit is contained in:
j3d1 2026-08-27 18:23:39 +02:00
parent 8cba1f52e8
commit ebd0db4b43
27 changed files with 326 additions and 329 deletions

View file

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

View file

@ -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'),
]

View file

@ -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')

View file

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

View file

@ -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/<id>/
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/<id>/
@api_view(['DELETE'])
@authentication_classes([SignatureAuthenticationLocal])
@permission_classes([IsAuthenticated])
def deleteFriendRequest(request, pk, format=None): # /api/friendrequests/<id>/
def deleteFriendRequest(request, pk, format=None):
user = request.user
get_object_or_404(user.friend_requests_incoming, pk=pk).delete()
user.save()

View file

@ -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/<name@domain>/
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/<name@domain>/members/<identity_id>/
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/<name@domain>/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/<name@domain
class GroupInvitesIncoming(APIView, ViewSetMixin):
"""/api/groupinvites/ - the invitee's own view of their pending invites, and the delivery
endpoint an inviter's client posts to directly on the invitee's own home backend (see
docs/design-in-progress/groups-mvp.md's invite/accept dance)."""
def get(self, request, format=None): # /api/groupinvites/ - only ever a local user checking their own invites
def get(self, request, format=None):
raw_request = request.body.decode('utf-8')
if not (user := authenticate_request_against_local_users(request, raw_request)):
return Response(status=status.HTTP_401_UNAUTHORIZED, data={'status': 'unauthorized'})
invites = user.group_invites_incoming.all()
return Response(GroupInviteIncomingSerializer(invites, many=True).data)
def post(self, request, format=None): # /api/groupinvites/ - delivery, self-certified, caller isn't known here
def post(self, request, format=None):
raw_request = request.body.decode('utf-8')
for field in ('group', 'inviter', 'inviter_key', 'invitee', 'secret'):
if field not in request.data:
@ -135,7 +131,7 @@ class GroupInvitesIncoming(APIView, ViewSetMixin):
@api_view(['DELETE'])
@authentication_classes([SignatureAuthenticationLocal])
@permission_classes([IsAuthenticated])
def declineGroupInvite(request, pk, format=None): # /api/groupinvites/<pk>/
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/<pk>/
@api_view(['POST'])
@authentication_classes([SignatureAuthenticationLocal])
@permission_classes([IsAuthenticated])
def recordGroupMembership(request, pk, format=None): # /api/groupinvites/<pk>/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/<pk>/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:

View file

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

View file

@ -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'),

View file

@ -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'),
]

View file

@ -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']])

View file

@ -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')

View file

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

View file

@ -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),
})

View file

@ -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

View file

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

View file

@ -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'])

View file

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

View file

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

View file

@ -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')

View file

@ -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')

View file

@ -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')

View file

@ -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:

View file

@ -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.`);

View file

@ -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
}

View file

@ -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: '/'});

View file

@ -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'));

View file

@ -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');