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() self.client = SignatureAuthClient()
def test_friend_local(self): 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) self.assertEqual(reply.status_code, 200)
def test_friend_external(self): 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) self.assertEqual(reply.status_code, 200)
def test_friend_fail(self): 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) self.assertEqual(reply.status_code, 403)
def test_friend_fail2(self): def test_friend_fail2(self):
target = "/api/friends/" target = "/api/v1/friends/"
signature = self.f['local_user1'].sign("http://testserver2" + target) signature = self.f['local_user1'].sign("http://testserver2" + target)
header = {'HTTP_AUTHORIZATION': 'Signature ' + str(self.f['local_user1']) + ':' + signature} header = {'HTTP_AUTHORIZATION': 'Signature ' + str(self.f['local_user1']) + ':' + signature}
reply = self.anonymous_client.get(target, **header) reply = self.anonymous_client.get(target, **header)
self.assertEqual(reply.status_code, 403) self.assertEqual(reply.status_code, 403)
def test_friend_fail3(self): def test_friend_fail3(self):
target = "/api/friends/" target = "/api/v1/friends/"
unknown_user = DummyExternalUser('extuser3', 'external.org', False) unknown_user = DummyExternalUser('extuser3', 'external.org', False)
signature = unknown_user.sign("http://testserver" + target) signature = unknown_user.sign("http://testserver" + target)
header = {'HTTP_AUTHORIZATION': 'Signature ' + str(unknown_user) + ':' + signature} header = {'HTTP_AUTHORIZATION': 'Signature ' + str(unknown_user) + ':' + signature}
@ -439,7 +439,7 @@ class FriendApiTestCase(UserTestMixin, ToolshedTestCase):
self.assertEqual(reply.status_code, 403) self.assertEqual(reply.status_code, 403)
def test_friend_fail4(self): def test_friend_fail4(self):
target = "/api/friends/" target = "/api/v1/friends/"
signature = self.f['local_user1'].sign("http://testserver" + target) signature = self.f['local_user1'].sign("http://testserver" + target)
header = {'HTTP_AUTHORIZATION': 'Auth ' + str(self.f['local_user1']) + ':' + signature} header = {'HTTP_AUTHORIZATION': 'Auth ' + str(self.f['local_user1']) + ':' + signature}
reply = self.anonymous_client.get(target, **header) reply = self.anonymous_client.get(target, **header)

View file

@ -19,13 +19,14 @@ urlpatterns = [
path('djangoadmin/', admin.site.urls), path('djangoadmin/', admin.site.urls),
path('auth/', include('authentication.api')), path('auth/', include('authentication.api')),
path('admin/', include('hostadmin.api')), path('admin/', include('hostadmin.api')),
path('api/', include('toolshed.api.friend')), path('api/version/', include('toolshed.api.version')),
path('api/', include('toolshed.api.group')), path('api/v1/', include('toolshed.api.info')),
path('api/', include('toolshed.api.idmap')), path('api/v1/', include('toolshed.api.friend')),
path('api/', include('toolshed.api.inventory')), path('api/v1/', include('toolshed.api.group')),
path('api/', include('toolshed.api.info')), path('api/v1/', include('toolshed.api.idmap')),
path('api/', include('toolshed.api.files')), path('api/v1/', include('toolshed.api.inventory')),
path('api/', include('toolshed.api.offlinedata')), path('api/v1/', include('toolshed.api.files')),
path('api/v1/', include('toolshed.api.offlinedata')),
path('media/', include('files.media_urls')), path('media/', include('files.media_urls')),
path('docs/', schema_view.with_ui('swagger', cache_timeout=0), name='api-docs'), 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() self.prepare_users()
def test_get_domains(self): 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.status_code, 200)
self.assertEqual(response.json(), ['example.com']) self.assertEqual(response.json(), ['example.com'])
@ -97,7 +97,7 @@ class CategoryApiTestCase(UserTestMixin, CategoryTestMixin, ToolshedTestCase):
self.prepare_categories() self.prepare_categories()
def test_get_categories(self): 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.status_code, 200)
self.assertEqual(response.json(), self.assertEqual(response.json(),
["cat1", "cat2", "cat3", "cat1/subcat1", ["cat1", "cat2", "cat3", "cat1/subcat1",
@ -215,7 +215,7 @@ class TagApiTestCase(UserTestMixin, CategoryTestMixin, TagTestMixin, ToolshedTes
self.prepare_tags() self.prepare_tags()
def test_get_tags(self): 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.status_code, 200)
self.assertEqual(response.json(), ["tag1", "tag2", "tag3"]) self.assertEqual(response.json(), ["tag1", "tag2", "tag3"])
@ -275,7 +275,7 @@ class PropertyApiTestCase(UserTestMixin, CategoryTestMixin, PropertyTestMixin, T
self.prepare_properties() self.prepare_properties()
def test_get_properties(self): 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(response.status_code, 200)
self.assertEqual(len(response.json()), 3) self.assertEqual(len(response.json()), 3)
self.assertEqual(response.json()[0]['name'], 'prop1') self.assertEqual(response.json()[0]['name'], 'prop1')

View file

@ -29,7 +29,7 @@ def _get_authorized_item(identity, item_id):
@api_view(['GET']) @api_view(['GET'])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@authentication_classes([SignatureAuthenticationLocal]) @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. # request.user is a ToolshedUser here; reach group membership via public_identity.
files = File.objects.select_related().filter( files = File.objects.select_related().filter(
Q(connected_items__owner=request.user) | Q(connected_items__owner=request.user) |
@ -94,7 +94,7 @@ def post_staged_file(request, workflow_id):
@api_view(['POST', 'GET']) @api_view(['POST', 'GET'])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@authentication_classes([SignatureAuthentication]) @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': if request.method == 'GET':
return get_item_files(request, item_id) return get_item_files(request, item_id)
elif request.method == 'POST': elif request.method == 'POST':
@ -104,7 +104,7 @@ def item_files(request, item_id, format=None): # /item_files/
@api_view(['DELETE']) @api_view(['DELETE'])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@authentication_classes([SignatureAuthentication]) @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) item = _get_authorized_item(request.user, item_id)
if item is None: if item is None:
return Response(status=status.HTTP_404_NOT_FOUND) 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']) @api_view(['POST', 'GET'])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@authentication_classes([SignatureAuthenticationLocal]) @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': if request.method == 'GET':
return get_staged_files(request, workflow_id) return get_staged_files(request, workflow_id)
elif request.method == 'POST': elif request.method == 'POST':
@ -133,7 +133,7 @@ def staged_files(request, workflow_id, format=None): # /staged_files/
@api_view(['DELETE']) @api_view(['DELETE'])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@authentication_classes([SignatureAuthenticationLocal]) @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: try:
workflow = WorkflowInstance.objects.get(id=workflow_id, owner=request.user) workflow = WorkflowInstance.objects.get(id=workflow_id, owner=request.user)
file = workflow.staged_files.get(hash=file_hash) file = workflow.staged_files.get(hash=file_hash)

View file

@ -19,13 +19,13 @@ class Friends(APIView, ViewSetMixin):
authentication_classes = [SignatureAuthentication] authentication_classes = [SignatureAuthentication]
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get(self, request, format=None): # /api/friends/ # def get(self, request, format=None):
user = request.user user = request.user
friends = user.friends.all() friends = user.friends.all()
serializer = FriendSerializer(friends, many=True) serializer = FriendSerializer(friends, many=True)
return Response(serializer.data) return Response(serializer.data)
def post(self, request, format=None): # /api/friends/ def post(self, request, format=None):
# only for local users # only for local users
try: try:
user = request.user user = request.user
@ -47,7 +47,7 @@ class Friends(APIView, ViewSetMixin):
class FriendsRequests(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') raw_request = request.body.decode('utf-8')
if user := authenticate_request_against_local_users(request, raw_request): if user := authenticate_request_against_local_users(request, raw_request):
friends_requests = user.friend_requests_incoming.all() friends_requests = user.friend_requests_incoming.all()
@ -56,7 +56,7 @@ class FriendsRequests(APIView, ViewSetMixin):
else: else:
return Response(status=status.HTTP_401_UNAUTHORIZED, data={'status': 'unauthorized'}) 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') raw_request = request.body.decode('utf-8')
if 'befriender' not in request.data or 'befriendee' not in request.data: if 'befriender' not in request.data or 'befriendee' not in request.data:
return Response(status=status.HTTP_400_BAD_REQUEST, data={'status': 'missing parameters'}) return Response(status=status.HTTP_400_BAD_REQUEST, data={'status': 'missing parameters'})
@ -131,7 +131,7 @@ class FriendsRequests(APIView, ViewSetMixin):
@api_view(['DELETE']) @api_view(['DELETE'])
@authentication_classes([SignatureAuthenticationLocal]) @authentication_classes([SignatureAuthenticationLocal])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
def dropFriend(request, pk, format=None): # /api/friends/<id>/ def dropFriend(request, pk, format=None):
user = request.user user = request.user
friend = get_object_or_404(user.friends, pk=pk) friend = get_object_or_404(user.friends, pk=pk)
user.friends.remove(friend) user.friends.remove(friend)
@ -142,7 +142,7 @@ def dropFriend(request, pk, format=None): # /api/friends/<id>/
@api_view(['DELETE']) @api_view(['DELETE'])
@authentication_classes([SignatureAuthenticationLocal]) @authentication_classes([SignatureAuthenticationLocal])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
def deleteFriendRequest(request, pk, format=None): # /api/friendrequests/<id>/ def deleteFriendRequest(request, pk, format=None):
user = request.user user = request.user
get_object_or_404(user.friend_requests_incoming, pk=pk).delete() get_object_or_404(user.friend_requests_incoming, pk=pk).delete()
user.save() user.save()

View file

@ -21,11 +21,11 @@ class Groups(APIView, ViewSetMixin):
authentication_classes = [SignatureAuthentication] authentication_classes = [SignatureAuthentication]
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get(self, request, format=None): # /api/groups/ def get(self, request, format=None):
groups = request.user.member_of_groups.all() groups = request.user.member_of_groups.all()
return Response(GroupSerializer(groups, many=True).data) 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') name = request.data.get('name')
if not name: if not name:
return Response(status=status.HTTP_400_BAD_REQUEST, data={'status': 'name is required'}) return Response(status=status.HTTP_400_BAD_REQUEST, data={'status': 'name is required'})
@ -40,7 +40,7 @@ class GroupDetail(APIView, ViewSetMixin):
authentication_classes = [SignatureAuthentication] authentication_classes = [SignatureAuthentication]
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get(self, request, handle, format=None): # /api/groups/<name@domain>/ def get(self, request, handle, format=None):
try: try:
name, domain = split_userhandle_or_throw(handle) name, domain = split_userhandle_or_throw(handle)
except ValueError: except ValueError:
@ -54,7 +54,7 @@ class GroupDetail(APIView, ViewSetMixin):
@api_view(['DELETE']) @api_view(['DELETE'])
@authentication_classes([SignatureAuthentication]) @authentication_classes([SignatureAuthentication])
@permission_classes([IsAuthenticated]) @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: try:
name, domain = split_userhandle_or_throw(handle) name, domain = split_userhandle_or_throw(handle)
except ValueError: except ValueError:
@ -73,7 +73,7 @@ def removeGroupMember(request, handle, identity_id, format=None): # /api/groups
@api_view(['POST']) @api_view(['POST'])
@authentication_classes([SignatureAuthentication]) @authentication_classes([SignatureAuthentication])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
def createGroupInvite(request, handle, format=None): # /api/groups/<name@domain>/invites/ def createGroupInvite(request, handle, format=None):
try: try:
name, domain = split_userhandle_or_throw(handle) name, domain = split_userhandle_or_throw(handle)
except ValueError: except ValueError:
@ -97,18 +97,14 @@ def createGroupInvite(request, handle, format=None): # /api/groups/<name@domain
class GroupInvitesIncoming(APIView, ViewSetMixin): class GroupInvitesIncoming(APIView, ViewSetMixin):
"""/api/groupinvites/ - the invitee's own view of their pending invites, and the delivery def get(self, request, format=None):
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
raw_request = request.body.decode('utf-8') raw_request = request.body.decode('utf-8')
if not (user := authenticate_request_against_local_users(request, raw_request)): if not (user := authenticate_request_against_local_users(request, raw_request)):
return Response(status=status.HTTP_401_UNAUTHORIZED, data={'status': 'unauthorized'}) return Response(status=status.HTTP_401_UNAUTHORIZED, data={'status': 'unauthorized'})
invites = user.group_invites_incoming.all() invites = user.group_invites_incoming.all()
return Response(GroupInviteIncomingSerializer(invites, many=True).data) 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') raw_request = request.body.decode('utf-8')
for field in ('group', 'inviter', 'inviter_key', 'invitee', 'secret'): for field in ('group', 'inviter', 'inviter_key', 'invitee', 'secret'):
if field not in request.data: if field not in request.data:
@ -135,7 +131,7 @@ class GroupInvitesIncoming(APIView, ViewSetMixin):
@api_view(['DELETE']) @api_view(['DELETE'])
@authentication_classes([SignatureAuthenticationLocal]) @authentication_classes([SignatureAuthenticationLocal])
@permission_classes([IsAuthenticated]) @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() get_object_or_404(request.user.group_invites_incoming, pk=pk).delete()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@ -143,12 +139,7 @@ def declineGroupInvite(request, pk, format=None): # /api/groupinvites/<pk>/
@api_view(['POST']) @api_view(['POST'])
@authentication_classes([SignatureAuthenticationLocal]) @authentication_classes([SignatureAuthenticationLocal])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
def recordGroupMembership(request, pk, format=None): # /api/groupinvites/<pk>/accept/ def recordGroupMembership(request, pk, format=None):
"""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."""
invite = get_object_or_404(request.user.group_invites_incoming, pk=pk) invite = get_object_or_404(request.user.group_invites_incoming, pk=pk)
membership, _ = GroupMembership.objects.get_or_create( membership, _ = GroupMembership.objects.get_or_create(
user=request.user, group_name=invite.group_name, group_domain=invite.group_domain) 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): 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] authentication_classes = [SignatureAuthenticationLocal]
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get(self, request, format=None): # /api/groupmemberships/ def get(self, request, format=None):
memberships = request.user.group_memberships.all() memberships = request.user.group_memberships.all()
return Response(GroupMembershipSerializer(memberships, many=True).data) return Response(GroupMembershipSerializer(memberships, many=True).data)
@api_view(['POST']) @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') raw_request = request.body.decode('utf-8')
for field in ('group', 'invitee', 'invitee_key', 'secret'): for field in ('group', 'invitee', 'invitee_key', 'secret'):
if field not in request.data: if field not in request.data:

View file

@ -15,7 +15,7 @@ class IdMap(APIView, ViewSetMixin):
authentication_classes = [SignatureAuthentication] authentication_classes = [SignatureAuthentication]
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get(self, request, format=None): # /api/idmap/ def get(self, request, format=None):
identity = request.user identity = request.user
identities = identity.friends.all() | KnownIdentity.objects.filter(pk=identity.pk) identities = identity.friends.all() | KnownIdentity.objects.filter(pk=identity.pk)
groups = identity.member_of_groups.all() groups = identity.member_of_groups.all()

View file

@ -7,20 +7,12 @@ from hostadmin.models import Domain
from authentication.signature_auth import SignatureAuthentication from authentication.signature_auth import SignatureAuthentication
from toolshed.models import Tag, Property, Category, InventoryItem from toolshed.models import Tag, Property, Category, InventoryItem
from toolshed.serializers import CategorySerializer, PropertySerializer from toolshed.serializers import CategorySerializer, PropertySerializer
from backend.settings import TOOLSHED_VERSION, GIT_COMMIT
@api_view(['GET']) @api_view(['GET'])
@permission_classes([]) @permission_classes([])
@authentication_classes([]) @authentication_classes([])
def get_version(request, format=None): # /version/ def list_domains(request, format=None):
return Response({'version': TOOLSHED_VERSION, 'commit': GIT_COMMIT})
@api_view(['GET'])
@permission_classes([])
@authentication_classes([])
def list_domains(request, format=None): # /domains/
domains = [domain.name for domain in Domain.objects.filter(open_registration=True)] domains = [domain.name for domain in Domain.objects.filter(open_registration=True)]
return Response(domains) return Response(domains)
@ -28,7 +20,7 @@ def list_domains(request, format=None): # /domains/
@api_view(['GET']) @api_view(['GET'])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@authentication_classes([SignatureAuthentication]) @authentication_classes([SignatureAuthentication])
def list_tags(format=None): # /tags/ def list_tags(format=None):
tags = [tag.name for tag in Tag.objects.all()] tags = [tag.name for tag in Tag.objects.all()]
return Response(tags) return Response(tags)
@ -36,28 +28,28 @@ def list_tags(format=None): # /tags/
@api_view(['GET']) @api_view(['GET'])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@authentication_classes([SignatureAuthentication]) @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) return Response(PropertySerializer(Property.objects.all(), many=True).data)
@api_view(['GET']) @api_view(['GET'])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@authentication_classes([SignatureAuthentication]) @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) return Response(CategorySerializer(Category.objects.all(), many=True).data)
@api_view(['GET']) @api_view(['GET'])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@authentication_classes([SignatureAuthentication]) @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) return Response(InventoryItem.AVAILABILITY_POLICY_CHOICES)
@api_view(['GET']) @api_view(['GET'])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@authentication_classes([SignatureAuthentication]) @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()] tags = [tag.name for tag in Tag.objects.all()]
properties = PropertySerializer(Property.objects.all(), many=True).data properties = PropertySerializer(Property.objects.all(), many=True).data
categories = [str(category) for category in Category.objects.all()] categories = [str(category) for category in Category.objects.all()]
@ -69,7 +61,7 @@ def combined_info(request, format=None): # /info/
urlpatterns = [ urlpatterns = [
path('version/', get_version, name='version'),
path('availability_policies/', list_availability_policies, name='availability_policies'), path('availability_policies/', list_availability_policies, name='availability_policies'),
path('properties/', list_properties, name='propertylist'), path('properties/', list_properties, name='propertylist'),
path('categories/', list_categories, name='categorylist'), 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}) self.assertEqual(response.json(), {'version': settings.TOOLSHED_VERSION, 'commit': settings.GIT_COMMIT})
def test_domains_anonymous(self): 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.status_code, 200)
self.assertEqual(response.json(), ['example.com']) self.assertEqual(response.json(), ['example.com'])
def test_domains_authenticated(self): 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.status_code, 200)
self.assertEqual(response.json(), ['example.com']) self.assertEqual(response.json(), ['example.com'])
def test_policy_api_anonymous(self): 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) self.assertEqual(response.status_code, 403)
def test_policy_api(self): 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.status_code, 200)
self.assertEqual(response.json(), [['sell', 'Sell'], ['rent', 'Rent'], ['lend', 'Lend'], ['share', 'Share'], self.assertEqual(response.json(), [['sell', 'Sell'], ['rent', 'Rent'], ['lend', 'Lend'], ['share', 'Share'],
['private', 'Private']]) ['private', 'Private']])
def test_combined_api_anonymous(self): 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) self.assertEqual(response.status_code, 403)
def test_combined_api(self): 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.status_code, 200)
self.assertEqual(response.json()['availability_policies'], [['sell', 'Sell'], ['rent', 'Rent'], ['lend', 'Lend'], self.assertEqual(response.json()['availability_policies'], [['sell', 'Sell'], ['rent', 'Rent'], ['lend', 'Lend'],
['share', 'Share'], ['private', 'Private']]) ['share', 'Share'], ['private', 'Private']])

View file

@ -32,7 +32,7 @@ class CategoryApiTestCase(CategoryTestMixin, UserTestMixin, ToolshedTestCase):
self.prepare_categories() self.prepare_categories()
def test_get_categories(self): 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 7) self.assertEqual(len(reply.json()), 7)
self.assertEqual(reply.json()[0], 'cat1') 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']) self.f['item2'].files.add(self.f['test_file1'])
def test_files_anonymous(self): 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) self.assertEqual(response.status_code, 403)
def test_list_all_files(self): 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(response.status_code, 200)
self.assertEqual(len(response.json()), 2) self.assertEqual(len(response.json()), 2)
self.assertEqual(response.json()[0]['mime_type'], 'text/plain') 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:]}") f"/media/{self.f['hash2'][:2]}/{self.f['hash2'][2:4]}/{self.f['hash2'][4:6]}/{self.f['hash2'][6:]}")
def test_files(self): 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(response.status_code, 200)
self.assertEqual(len(response.json()), 2) self.assertEqual(len(response.json()), 2)
self.assertEqual(response.json()[0]['mime_type'], 'text/plain') 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:]}") 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): 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) self.assertEqual(response.status_code, 404)
def test_post_file(self): 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'}) {'data': self.f['encoded_content4'], 'mime_type': 'text/plain'})
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
self.assertEqual(File.objects.count(), 4) self.assertEqual(File.objects.count(), 4)
@ -68,7 +68,7 @@ class FileApiTestCase(UserTestMixin, FilesTestMixin, InventoryTestMixin, Toolshe
def test_post_file_duplicate(self): def test_post_file_duplicate(self):
self.assertEqual(File.objects.count(), 3) self.assertEqual(File.objects.count(), 3)
self.assertEqual(self.f['item1'].files.count(), 2) 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'}) {'data': self.f['encoded_content3'], 'mime_type': 'text/plain'})
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
self.assertEqual(File.objects.count(), 3) 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:]}") 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): 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']}) {'data': self.f['encoded_content4']})
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
def test_post_file_not_found_item(self): 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'}) {'data': self.f['encoded_content3'], 'mime_type': 'text/plain'})
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
self.assertEqual(File.objects.count(), 3) self.assertEqual(File.objects.count(), 3)
def test_post_file_not_authenticated(self): 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'}) {'data': self.f['encoded_content3'], 'mime_type': 'text/plain'})
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
self.assertEqual(File.objects.count(), 3) self.assertEqual(File.objects.count(), 3)
def test_post_file_not_authorized(self): 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'}) {'data': self.f['encoded_content3'], 'mime_type': 'text/plain'})
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
self.assertEqual(File.objects.count(), 3) self.assertEqual(File.objects.count(), 3)
def test_delete_file(self): 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.f['local_user1'])
self.assertEqual(response.status_code, 204) self.assertEqual(response.status_code, 204)
self.assertEqual(File.objects.count(), 3) self.assertEqual(File.objects.count(), 3)
self.assertEqual(self.f['item1'].files.count(), 1) self.assertEqual(self.f['item1'].files.count(), 1)
def test_delete_file_last_use(self): 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.f['local_user1'])
self.assertEqual(response.status_code, 204) self.assertEqual(response.status_code, 204)
self.assertEqual(File.objects.count(), 2) self.assertEqual(File.objects.count(), 2)
self.assertEqual(self.f['item1'].files.count(), 1) self.assertEqual(self.f['item1'].files.count(), 1)
def test_delete_file_not_found(self): 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(response.status_code, 404)
self.assertEqual(File.objects.count(), 3) self.assertEqual(File.objects.count(), 3)
self.assertEqual(self.f['item1'].files.count(), 2) self.assertEqual(self.f['item1'].files.count(), 2)
def test_delete_file_not_found_item(self): 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(response.status_code, 404)
self.assertEqual(File.objects.count(), 3) self.assertEqual(File.objects.count(), 3)
self.assertEqual(self.f['item1'].files.count(), 2) self.assertEqual(self.f['item1'].files.count(), 2)
def test_delete_file_not_owner(self): 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.f['local_user2'])
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
self.assertEqual(File.objects.count(), 3) self.assertEqual(File.objects.count(), 3)
self.assertEqual(self.f['item1'].files.count(), 2) self.assertEqual(self.f['item1'].files.count(), 2)
def test_delete_file_anonymous(self): 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(response.status_code, 403)
self.assertEqual(File.objects.count(), 3) self.assertEqual(File.objects.count(), 3)
self.assertEqual(self.f['item1'].files.count(), 2) self.assertEqual(self.f['item1'].files.count(), 2)
def test_get_inventory(self): 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 2) self.assertEqual(len(reply.json()), 2)
self.assertEqual(reply.json()[0]['name'], 'test1') 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']) self.f['group_item'].files.add(self.f['test_file1'])
def test_get_group_item_files(self): 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(response.status_code, 200)
self.assertEqual(len(response.json()), 1) self.assertEqual(len(response.json()), 1)
def test_other_member_can_post_file(self): 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'}) {'data': self.f['encoded_content4'], 'mime_type': 'text/plain'})
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
self.assertEqual(self.f['group_item'].files.count(), 2) self.assertEqual(self.f['group_item'].files.count(), 2)
def test_remote_member_without_local_account_can_post_file(self): def test_remote_member_without_local_account_can_post_file(self):
self.f['group1'].members.add(self.f['ext_user1'].public_identity) 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'}) {'data': self.f['encoded_content4'], 'mime_type': 'text/plain'})
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
self.assertEqual(self.f['group_item'].files.count(), 2) self.assertEqual(self.f['group_item'].files.count(), 2)
def test_remote_member_without_local_account_can_get_files(self): def test_remote_member_without_local_account_can_get_files(self):
self.f['group1'].members.add(self.f['ext_user1'].public_identity) 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) self.assertEqual(response.status_code, 200)
def test_non_member_cannot_post_file(self): 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'}) {'data': self.f['encoded_content4'], 'mime_type': 'text/plain'})
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
self.assertEqual(self.f['group_item'].files.count(), 1) self.assertEqual(self.f['group_item'].files.count(), 1)
def test_non_member_cannot_get_files(self): 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) self.assertEqual(response.status_code, 404)
def test_other_member_can_delete_file(self): 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.f['local_user2'])
self.assertEqual(response.status_code, 204) self.assertEqual(response.status_code, 204)
self.assertEqual(self.f['group_item'].files.count(), 0) self.assertEqual(self.f['group_item'].files.count(), 0)
def test_non_member_cannot_delete_file(self): 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.f['ext_user1'])
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
self.assertEqual(self.f['group_item'].files.count(), 1) 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') owner_group=group2, owned_quantity=1, name='group2-drill', availability_policy='private')
self.assertEqual(self.f['group_item'].id, group2_item.id) 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) self.assertEqual(response.status_code, 200)
def test_group_item_files_listed_in_all_files(self): 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(response.status_code, 200)
self.assertEqual(len(response.json()), 1) self.assertEqual(len(response.json()), 1)
self.assertEqual(response.json()[0]['id'], self.f['test_file1'].id) 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) self.f['ext_user1'].friends.add(self.f['local_user1'].public_identity)
def test_friend_list_internal1(self): 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 2) self.assertEqual(len(reply.json()), 2)
self.assertEqual(reply.json()[0]['handle'], str(self.f['local_user2'])) self.assertEqual(reply.json()[0]['handle'], str(self.f['local_user2']))
self.assertEqual(reply.json()[1]['handle'], str(self.f['ext_user1'])) self.assertEqual(reply.json()[1]['handle'], str(self.f['ext_user1']))
def test_friend_list_internal2(self): 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 1) self.assertEqual(len(reply.json()), 1)
self.assertEqual(reply.json()[0]['handle'], str(self.f['local_user1'])) self.assertEqual(reply.json()[0]['handle'], str(self.f['local_user1']))
def test_friend_list_external(self): 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 1) self.assertEqual(len(reply.json()), 1)
self.assertEqual(reply.json()[0]['handle'], str(self.f['local_user1'])) self.assertEqual(reply.json()[0]['handle'], str(self.f['local_user1']))
def test_friend_delete(self): 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.f['local_user1'])
self.assertEqual(reply.status_code, 204) self.assertEqual(reply.status_code, 204)
self.assertEqual(self.f['local_user1'].friends.count(), 1) self.assertEqual(self.f['local_user1'].friends.count(), 1)
@ -101,29 +101,29 @@ class FriendRequestListTestCase(UserTestMixin, ToolshedTestCase):
self.friendrequest1.save() self.friendrequest1.save()
def test_friend_request_withouth_auth(self): def test_friend_request_withouth_auth(self):
reply = Client().get('/api/friendrequests/') reply = Client().get('/api/v1/friendrequests/')
self.assertEqual(reply.status_code, 401) self.assertEqual(reply.status_code, 401)
def test_friend_request_empty(self): 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.status_code, 200)
self.assertEqual(reply.json(), []) self.assertEqual(reply.json(), [])
def test_friend_request_list(self): 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 1) self.assertEqual(len(reply.json()), 1)
self.assertEqual(reply.json()[0]['befriender'], str(self.f['ext_user2'])) 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()) self.assertEqual(reply.json()[0]['befriender_public_key'], self.f['ext_user2'].public_key())
def test_delete_friend_request(self): 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.f['local_user1'])
self.assertEqual(reply.status_code, 204) self.assertEqual(reply.status_code, 204)
self.assertEqual(FriendRequestIncoming.objects.count(), 0) self.assertEqual(FriendRequestIncoming.objects.count(), 0)
def test_delete_friend_request_not_found(self): 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(reply.status_code, 404)
self.assertEqual(FriendRequestIncoming.objects.count(), 1) self.assertEqual(FriendRequestIncoming.objects.count(), 1)
@ -140,7 +140,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase):
def test_post_request(self): def test_post_request(self):
befriender = self.f['ext_user1'] befriender = self.f['ext_user1']
befriendee = 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': str(befriender),
'befriender_key': befriender.public_key(), 'befriender_key': befriender.public_key(),
'befriendee': str(befriendee), 'befriendee': str(befriendee),
@ -158,7 +158,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase):
def test_post_request_local(self): def test_post_request_local(self):
befriender = self.f['local_user2'] befriender = self.f['local_user2']
befriendee = 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': str(befriender),
'befriendee': str(befriendee), 'befriendee': str(befriendee),
# 'secret': 'secret2' # 'secret': 'secret2'
@ -173,14 +173,14 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase):
# self.assertEqual(incoming.secret, 'secret2') # self.assertEqual(incoming.secret, 'secret2')
def test_post_request_withouth_auth(self): def test_post_request_withouth_auth(self):
reply = Client().post('/api/friendrequests/') reply = Client().post('/api/v1/friendrequests/')
self.assertEqual(reply.status_code, 400) self.assertEqual(reply.status_code, 400)
def test_post_request_broken_header(self): def test_post_request_broken_header(self):
befriender = self.f['ext_user1'] befriender = self.f['ext_user1']
befriendee = self.f['local_user1'] befriendee = self.f['local_user1']
broken_client = SignatureAuthClient(header_prefix='broken ') 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': str(befriender),
'befriender_key': befriender.public_key(), 'befriender_key': befriender.public_key(),
'befriendee': str(befriendee), 'befriendee': str(befriendee),
@ -191,7 +191,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase):
def test_post_request_missing_key(self): def test_post_request_missing_key(self):
befriender = self.f['ext_user1'] befriender = self.f['ext_user1']
befriendee = 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': str(befriender),
'befriendee': str(befriendee), 'befriendee': str(befriendee),
'secret': 'secret2' 'secret': 'secret2'
@ -201,7 +201,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase):
def test_post_request_missing_key_none(self): def test_post_request_missing_key_none(self):
befriender = self.f['ext_user1'] befriender = self.f['ext_user1']
befriendee = 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': str(befriender),
'befriendee': str(befriendee), 'befriendee': str(befriendee),
'befriender_key': None, 'befriender_key': None,
@ -212,7 +212,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase):
def test_post_request_breaking_key(self): def test_post_request_breaking_key(self):
befriender = self.f['ext_user1'] befriender = self.f['ext_user1']
befriendee = 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': str(befriender),
'befriendee': str(befriendee), 'befriendee': str(befriendee),
'secret': 'secret2', 'secret': 'secret2',
@ -223,7 +223,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase):
def test_post_request_wrong_befriender(self): def test_post_request_wrong_befriender(self):
befriender = self.f['ext_user1'] befriender = self.f['ext_user1']
befriendee = self.f['local_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': str(self.f['local_user2']),
'befriender_key': befriender.public_key(), 'befriender_key': befriender.public_key(),
'befriendee': str(befriendee), 'befriendee': str(befriendee),
@ -235,7 +235,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase):
befriender = self.f['ext_user1'] befriender = self.f['ext_user1']
befriendee = self.f['local_user1'] befriendee = self.f['local_user1']
bad_signature = SignatureAuthClient(bad_signature=True) 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': str(befriender),
'befriender_key': befriender.public_key(), 'befriender_key': befriender.public_key(),
'befriendee': str(befriendee), 'befriendee': str(befriendee),
@ -246,7 +246,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase):
def test_post_request_self(self): def test_post_request_self(self):
befriender = self.f['local_user1'] befriender = self.f['local_user1']
befriendee = 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': str(befriender),
'befriender_key': befriender.public_key(), 'befriender_key': befriender.public_key(),
'befriendee': str(befriendee), 'befriendee': str(befriendee),
@ -257,7 +257,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase):
def test_post_request_befreindee_not_found(self): def test_post_request_befreindee_not_found(self):
befriender = self.f['ext_user1'] befriender = self.f['ext_user1']
befriendee = 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': str(befriender),
'befriender_key': befriender.public_key(), 'befriender_key': befriender.public_key(),
'befriendee': 'nonexistent@' + befriendee.domain, 'befriendee': 'nonexistent@' + befriendee.domain,
@ -268,7 +268,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase):
def test_post_request_missing_secret(self): def test_post_request_missing_secret(self):
befriender = self.f['ext_user1'] befriender = self.f['ext_user1']
befriendee = 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': str(befriender),
'befriender_key': befriender.public_key(), 'befriender_key': befriender.public_key(),
'befriendee': str(befriendee) 'befriendee': str(befriendee)
@ -281,7 +281,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase):
request = FriendRequestIncoming.objects.filter(befriender_username=befriender.username, request = FriendRequestIncoming.objects.filter(befriender_username=befriender.username,
befriender_domain=befriender.domain, befriender_domain=befriender.domain,
befriendee_user=befriendee).first() befriendee_user=befriendee).first()
reply = client.post('/api/friends/', befriendee, { reply = client.post('/api/v1/friends/', befriendee, {
'friend_request_id': request.id, 'friend_request_id': request.id,
'secret': request.secret 'secret': request.secret
}) })
@ -294,7 +294,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase):
request = FriendRequestIncoming.objects.filter(befriender_username=befriender.username, request = FriendRequestIncoming.objects.filter(befriender_username=befriender.username,
befriender_domain=befriender.domain, befriender_domain=befriender.domain,
befriendee_user=befriendee).first() befriendee_user=befriendee).first()
reply = client.post('/api/friends/', befriendee, { reply = client.post('/api/v1/friends/', befriendee, {
'friend_request_id': request.id, 'friend_request_id': request.id,
'secret': request.secret 'secret': request.secret
}) })
@ -304,7 +304,7 @@ class FriendRequestIncomingTestCase(UserTestMixin, ToolshedTestCase):
def test_accept_request_not_found(self): def test_accept_request_not_found(self):
befriender = self.f['ext_user2'] befriender = self.f['ext_user2']
befriendee = self.f['local_user1'] befriendee = self.f['local_user1']
reply = client.post('/api/friends/', befriendee, { reply = client.post('/api/v1/friends/', befriendee, {
'friend_request_id': 999, 'friend_request_id': 999,
'secret': 'secret1' 'secret': 'secret1'
}) })
@ -326,7 +326,7 @@ class FriendRequestOutgoingTestCase(UserTestMixin, ToolshedTestCase):
def test_post_outgoing_friend_request(self): def test_post_outgoing_friend_request(self):
befriender = self.f['local_user1'] befriender = self.f['local_user1']
befriendee = self.f['ext_user1'] befriendee = self.f['ext_user1']
reply = client.post('/api/friendrequests/', befriender, { reply = client.post('/api/v1/friendrequests/', befriender, {
'befriender': str(befriender), 'befriender': str(befriender),
'befriendee': str(befriendee), 'befriendee': str(befriendee),
}) })
@ -343,7 +343,7 @@ class FriendRequestOutgoingTestCase(UserTestMixin, ToolshedTestCase):
def test_accept_request(self): def test_accept_request(self):
befriender = self.f['ext_user1'] befriender = self.f['ext_user1']
befriendee = self.f['local_user2'] befriendee = self.f['local_user2']
reply = client.post('/api/friendrequests/', befriender, { reply = client.post('/api/v1/friendrequests/', befriender, {
'befriender': str(befriender), 'befriender': str(befriender),
'befriender_key': befriender.public_key(), 'befriender_key': befriender.public_key(),
'befriendee': str(befriendee), 'befriendee': str(befriendee),
@ -367,12 +367,12 @@ class FriendRequestCombinedTestCase(UserTestMixin, ToolshedTestCase):
def test_friend_request_combined(self): def test_friend_request_combined(self):
befriender = self.f['local_user1'] befriender = self.f['local_user1']
befriendee = self.f['local_user2'] befriendee = self.f['local_user2']
reply1 = client.post('/api/friendrequests/', befriender, { reply1 = client.post('/api/v1/friendrequests/', befriender, {
'befriender': str(befriender), 'befriender': str(befriender),
'befriendee': str(befriendee), 'befriendee': str(befriendee),
}) })
secret = reply1.json()['secret'] secret = reply1.json()['secret']
reply2 = client.post('/api/friendrequests/', befriender, { reply2 = client.post('/api/v1/friendrequests/', befriender, {
'befriender': str(befriender), 'befriender': str(befriender),
'befriender_key': befriender.public_key(), 'befriender_key': befriender.public_key(),
'befriendee': str(befriendee), 'befriendee': str(befriendee),
@ -389,7 +389,7 @@ class FriendRequestCombinedTestCase(UserTestMixin, ToolshedTestCase):
befriender = self.f['local_user1'] befriender = self.f['local_user1']
befriendee = self.f['local_user2'] befriendee = self.f['local_user2']
befriender.friends.add(befriendee.public_identity) befriender.friends.add(befriendee.public_identity)
reply1 = client.post('/api/friendrequests/', befriender, { reply1 = client.post('/api/v1/friendrequests/', befriender, {
'befriender': str(befriender), 'befriender': str(befriender),
'befriendee': str(befriendee), 'befriendee': str(befriendee),
}) })

View file

@ -27,34 +27,34 @@ class GroupApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase):
self.prepare_users() self.prepare_users()
def test_create_group(self): 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) self.assertEqual(reply.status_code, 201)
group = Group.objects.get(name='workshop') group = Group.objects.get(name='workshop')
self.assertEqual(group.domain, self.f['local_user1'].domain) self.assertEqual(group.domain, self.f['local_user1'].domain)
self.assertTrue(group.is_member(self.f['local_user1'].public_identity)) self.assertTrue(group.is_member(self.f['local_user1'].public_identity))
def test_create_group_duplicate_name(self): def test_create_group_duplicate_name(self):
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/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) self.assertEqual(reply.status_code, 409)
def test_create_group_missing_name(self): 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) self.assertEqual(reply.status_code, 400)
def test_list_groups(self): def test_list_groups(self):
self.prepare_groups() 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 1) 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(reply2.status_code, 200)
self.assertEqual(len(reply2.json()), 0) self.assertEqual(len(reply2.json()), 0)
def test_group_detail_member(self): def test_group_detail_member(self):
self.prepare_groups() self.prepare_groups()
group = self.f['group1'] 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.status_code, 200)
self.assertEqual(reply.json()['handle'], str(group)) self.assertEqual(reply.json()['handle'], str(group))
self.assertEqual(len(reply.json()['members']), 1) self.assertEqual(len(reply.json()['members']), 1)
@ -62,18 +62,18 @@ class GroupApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase):
def test_group_detail_non_member(self): def test_group_detail_non_member(self):
self.prepare_groups() self.prepare_groups()
group = self.f['group1'] 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) self.assertEqual(reply.status_code, 404)
def test_group_detail_no_such_group(self): 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) self.assertEqual(reply.status_code, 404)
def test_remove_member(self): def test_remove_member(self):
self.prepare_groups() self.prepare_groups()
group = self.f['group1'] group = self.f['group1']
group.members.add(self.f['local_user2'].public_identity) 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']) group.name, group.domain, self.f['local_user2'].public_identity.id), self.f['local_user1'])
self.assertEqual(reply.status_code, 204) self.assertEqual(reply.status_code, 204)
self.assertEqual(group.members.count(), 1) self.assertEqual(group.members.count(), 1)
@ -81,7 +81,7 @@ class GroupApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase):
def test_remove_last_member_blocked(self): def test_remove_last_member_blocked(self):
self.prepare_groups() self.prepare_groups()
group = self.f['group1'] 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']) group.name, group.domain, self.f['local_user1'].public_identity.id), self.f['local_user1'])
self.assertEqual(reply.status_code, 400) self.assertEqual(reply.status_code, 400)
self.assertEqual(group.members.count(), 1) self.assertEqual(group.members.count(), 1)
@ -90,7 +90,7 @@ class GroupApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase):
self.prepare_groups() self.prepare_groups()
group = self.f['group1'] group = self.f['group1']
group.members.add(self.f['local_user2'].public_identity) 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']) group.name, group.domain, self.f['local_user1'].public_identity.id), self.f['ext_user1'])
self.assertEqual(reply.status_code, 404) self.assertEqual(reply.status_code, 404)
self.assertEqual(group.members.count(), 2) self.assertEqual(group.members.count(), 2)
@ -107,13 +107,13 @@ class GroupInviteApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase):
inviter = self.f['local_user1'] inviter = self.f['local_user1']
invitee = self.f['local_user2'] 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)}) {'invitee': str(invitee)})
self.assertEqual(reply1.status_code, 201) self.assertEqual(reply1.status_code, 201)
secret = reply1.json()['secret'] secret = reply1.json()['secret']
self.assertEqual(GroupInvite.objects.count(), 1) 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(), 'group': str(group), 'inviter': str(inviter), 'inviter_key': inviter.public_key(),
'invitee': str(invitee), 'secret': secret 'invitee': str(invitee), 'secret': secret
}) })
@ -121,20 +121,20 @@ class GroupInviteApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase):
incoming = GroupInviteIncoming.objects.get() incoming = GroupInviteIncoming.objects.get()
self.assertEqual(incoming.invitee_user, invitee) 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(reply3.status_code, 200)
self.assertEqual(len(reply3.json()), 1) self.assertEqual(len(reply3.json()), 1)
self.assertEqual(reply3.json()[0]['group'], str(group)) self.assertEqual(reply3.json()[0]['group'], str(group))
self.assertEqual(reply3.json()[0]['inviter'], str(inviter)) 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 'group': str(group), 'invitee': str(invitee), 'invitee_key': invitee.public_key(), 'secret': secret
}) })
self.assertEqual(reply4.status_code, 201) self.assertEqual(reply4.status_code, 201)
self.assertTrue(group.is_member(invitee.public_identity)) self.assertTrue(group.is_member(invitee.public_identity))
self.assertEqual(GroupInvite.objects.count(), 0) 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.status_code, 201)
self.assertEqual(reply5.json()['handle'], str(group)) self.assertEqual(reply5.json()['handle'], str(group))
self.assertEqual(GroupInviteIncoming.objects.count(), 0) self.assertEqual(GroupInviteIncoming.objects.count(), 0)
@ -144,7 +144,7 @@ class GroupInviteApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase):
def test_invite_non_member_denied(self): def test_invite_non_member_denied(self):
group = self.f['group1'] 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'])}) {'invitee': str(self.f['ext_user1'])})
self.assertEqual(reply.status_code, 404) self.assertEqual(reply.status_code, 404)
self.assertEqual(GroupInvite.objects.count(), 0) self.assertEqual(GroupInvite.objects.count(), 0)
@ -152,7 +152,7 @@ class GroupInviteApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase):
def test_invite_already_member(self): def test_invite_already_member(self):
group = self.f['group1'] group = self.f['group1']
group.members.add(self.f['local_user2'].public_identity) 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'])}) {'invitee': str(self.f['local_user2'])})
self.assertEqual(reply.status_code, 208) 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, 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, inviter_username=self.f['local_user1'].username, inviter_domain=self.f['local_user1'].domain,
invitee_user=self.f['local_user2'], secret='some-secret') 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(reply.status_code, 204)
self.assertEqual(GroupInviteIncoming.objects.count(), 0) self.assertEqual(GroupInviteIncoming.objects.count(), 0)
@ -169,8 +169,8 @@ class GroupInviteApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase):
group = self.f['group1'] group = self.f['group1']
inviter = self.f['local_user1'] inviter = self.f['local_user1']
invitee = self.f['local_user2'] invitee = self.f['local_user2']
client.post('/api/groups/{}@{}/invites/'.format(group.name, group.domain), inviter, {'invitee': str(invitee)}) client.post('/api/v1/groups/{}@{}/invites/'.format(group.name, group.domain), inviter, {'invitee': str(invitee)})
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(), 'group': str(group), 'invitee': str(invitee), 'invitee_key': invitee.public_key(),
'secret': 'not-the-right-secret' 'secret': 'not-the-right-secret'
}) })
@ -181,11 +181,11 @@ class GroupInviteApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase):
group = self.f['group1'] group = self.f['group1']
inviter = self.f['local_user1'] inviter = self.f['local_user1']
invitee = DummyExternalUser('newmember', 'remote.example', known=False) 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)}) {'invitee': str(invitee)})
secret = reply1.json()['secret'] 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 'group': str(group), 'invitee': str(invitee), 'invitee_key': invitee.public_key(), 'secret': secret
}) })
self.assertEqual(reply.status_code, 201) self.assertEqual(reply.status_code, 201)
@ -199,11 +199,11 @@ class GroupInviteApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase):
group = self.f['group1'] group = self.f['group1']
inviter = self.f['local_user1'] inviter = self.f['local_user1']
invitee = self.f['local_user2'] 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)}) {'invitee': str(invitee)})
secret = reply1.json()['secret'] secret = reply1.json()['secret']
bad_signature_client = SignatureAuthClient(bad_signature=True) 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 'group': str(group), 'invitee': str(invitee), 'invitee_key': invitee.public_key(), 'secret': secret
}) })
self.assertEqual(reply.status_code, 401) self.assertEqual(reply.status_code, 401)
@ -230,7 +230,7 @@ class GroupMembershipApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase
inviter_username='someone', inviter_domain='other.example', inviter_username='someone', inviter_domain='other.example',
invitee_user=invitee, secret='some-secret') 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.status_code, 201)
self.assertEqual(reply.json()['handle'], '#remoteworkshop@other.example') self.assertEqual(reply.json()['handle'], '#remoteworkshop@other.example')
@ -245,7 +245,7 @@ class GroupMembershipApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase
inviter_username='someone', inviter_domain='other.example', inviter_username='someone', inviter_domain='other.example',
invitee_user=self.f['local_user2'], secret='some-secret') 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(reply.status_code, 404)
self.assertEqual(GroupMembership.objects.count(), 0) self.assertEqual(GroupMembership.objects.count(), 0)
@ -257,7 +257,7 @@ class GroupMembershipApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase
inviter_username='someone', inviter_domain='other.example', inviter_username='someone', inviter_domain='other.example',
invitee_user=self.f['local_user2'], secret='some-secret') 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(reply.status_code, 204)
self.assertEqual(GroupMembership.objects.count(), 0) self.assertEqual(GroupMembership.objects.count(), 0)
@ -273,7 +273,7 @@ class GroupMembershipApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase
inviter_username='someone', inviter_domain='other.example', inviter_username='someone', inviter_domain='other.example',
invitee_user=invitee, secret='some-secret') 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.status_code, 201)
self.assertEqual(GroupMembership.objects.filter(user=invitee).count(), 1) self.assertEqual(GroupMembership.objects.filter(user=invitee).count(), 1)
@ -283,12 +283,12 @@ class GroupMembershipApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase
GroupMembership.objects.create( GroupMembership.objects.create(
user=self.f['local_user1'], group_name='remoteworkshop', group_domain='other.example') 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 1) self.assertEqual(len(reply.json()), 1)
self.assertEqual(reply.json()[0]['handle'], '#remoteworkshop@other.example') 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(reply2.status_code, 200)
self.assertEqual(len(reply2.json()), 0) self.assertEqual(len(reply2.json()), 0)
@ -296,7 +296,7 @@ class GroupMembershipApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestCase
GroupMembership.objects.create( GroupMembership.objects.create(
user=self.f['local_user1'], group_name='remoteworkshop', group_domain='other.example') 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 # 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 # 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() self.prepare_users()
def test_idmap_includes_self(self): 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) self.assertEqual(reply.status_code, 200)
identities = reply.json()['identities'] identities = reply.json()['identities']
self.assertIn(str(self.f['local_user1']), [i['handle'] for i in identities]) self.assertIn(str(self.f['local_user1']), [i['handle'] for i in identities])
def test_idmap_includes_friends(self): def test_idmap_includes_friends(self):
self.f['local_user1'].friends.add(self.f['local_user2'].public_identity) 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) self.assertEqual(reply.status_code, 200)
identities = reply.json()['identities'] identities = reply.json()['identities']
self.assertIn(str(self.f['local_user2']), [i['handle'] for i in identities]) self.assertIn(str(self.f['local_user2']), [i['handle'] for i in identities])
def test_idmap_excludes_non_friends(self): 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) self.assertEqual(reply.status_code, 200)
identities = reply.json()['identities'] identities = reply.json()['identities']
self.assertNotIn(str(self.f['local_user2']), [i['handle'] for i in identities]) self.assertNotIn(str(self.f['local_user2']), [i['handle'] for i in identities])
def test_idmap_includes_member_groups(self): def test_idmap_includes_member_groups(self):
self.prepare_groups() 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) self.assertEqual(reply.status_code, 200)
groups = reply.json()['groups'] groups = reply.json()['groups']
self.assertIn(str(self.f['group1']), [g['handle'] for g in groups]) self.assertIn(str(self.f['group1']), [g['handle'] for g in groups])
def test_idmap_excludes_non_member_groups(self): def test_idmap_excludes_non_member_groups(self):
self.prepare_groups() 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) self.assertEqual(reply.status_code, 200)
groups = reply.json()['groups'] groups = reply.json()['groups']
self.assertNotIn(str(self.f['group1']), [g['handle'] for g in groups]) self.assertNotIn(str(self.f['group1']), [g['handle'] for g in groups])
def test_idmap_unauthenticated(self): def test_idmap_unauthenticated(self):
reply = Client().get('/api/idmap/') reply = Client().get('/api/v1/idmap/')
self.assertEqual(reply.status_code, 403) 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 self.owner_id = self.f['local_user1'].public_identity.pk
def test_resolve_item_as_owner(self): 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.f['local_user1'])
self.assertEqual(reply.status_code, 200) self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.json(), {'handle': 'testuser1@example.com', 'id': self.f['item1'].id}) self.assertEqual(reply.json(), {'handle': 'testuser1@example.com', 'id': self.f['item1'].id})
def test_resolve_item_as_friend(self): 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.f['local_user2'])
self.assertEqual(reply.status_code, 200) self.assertEqual(reply.status_code, 200)
def test_resolve_item_not_friend(self): 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.f['ext_user1'])
self.assertEqual(reply.status_code, 403) self.assertEqual(reply.status_code, 403)
def test_resolve_item_private_not_owner(self): def test_resolve_item_private_not_owner(self):
private_item = InventoryItem.create_for_owner( private_item = InventoryItem.create_for_owner(
owner=self.f['local_user1'], owned_quantity=1, name='secret', availability_policy='private') 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.f['local_user2'])
self.assertEqual(reply.status_code, 403) 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.f['local_user1'])
self.assertEqual(reply.status_code, 200) self.assertEqual(reply.status_code, 200)
def test_resolve_item_unknown_owner(self): 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) self.assertEqual(reply.status_code, 404)
def test_resolve_item_unknown_local_id(self): 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) self.assertEqual(reply.status_code, 404)
def test_resolve_unsupported_kind(self): def test_resolve_unsupported_kind(self):
# workflow/category/file/group aren't wired up yet - see # 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. # 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) self.assertEqual(reply.status_code, 400)
def test_resolve_storage_location_as_owner(self): 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.f['local_user1'])
self.assertEqual(reply.status_code, 200) self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.json(), {'handle': 'testuser1@example.com', 'id': self.f['loc1'].id}) self.assertEqual(reply.json(), {'handle': 'testuser1@example.com', 'id': self.f['loc1'].id})
def test_resolve_storage_location_unknown_local_id(self): 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.f['local_user1'])
self.assertEqual(reply.status_code, 404) self.assertEqual(reply.status_code, 404)
def test_resolve_group_item_as_member(self): def test_resolve_group_item_as_member(self):
item = InventoryItem.create_for_owner( item = InventoryItem.create_for_owner(
owner_group=self.f['group1'], owned_quantity=1, name='drill', availability_policy='private') 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.f['local_user1'])
self.assertEqual(reply.status_code, 200) self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.json(), {'handle': str(self.f['group1']), 'id': item.id}) 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): def test_resolve_group_item_non_member_denied(self):
item = InventoryItem.create_for_owner( item = InventoryItem.create_for_owner(
owner_group=self.f['group1'], owned_quantity=1, name='drill', availability_policy='private') 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.f['local_user2'])
self.assertEqual(reply.status_code, 403) self.assertEqual(reply.status_code, 403)
def test_resolve_group_storage_location_as_member(self): def test_resolve_group_storage_location_as_member(self):
location = StorageLocation.create_for_owner(owner_group=self.f['group1'], name='shelf') location = StorageLocation.create_for_owner(owner_group=self.f['group1'], name='shelf')
reply = client.get( 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.f['local_user1'])
self.assertEqual(reply.status_code, 200) self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.json(), {'handle': str(self.f['group1']), 'id': location.id}) self.assertEqual(reply.json(), {'handle': str(self.f['group1']), 'id': location.id})
def test_resolve_unknown_group(self): 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) 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']) self.own_handle = str(self.f['local_user1'])
def test_get_inventory(self): 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 2) self.assertEqual(len(reply.json()), 2)
self.assertEqual(reply.json()[0]['name'], 'test1') self.assertEqual(reply.json()[0]['name'], 'test1')
@ -39,7 +39,7 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
self.assertEqual(reply.json()[1]['availability_policy'], 'friends') self.assertEqual(reply.json()[1]['availability_policy'], 'friends')
def test_post_new_item(self): 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', 'availability_policy': 'rent',
'category': 'cat2', 'category': 'cat2',
'name': 'test3', 'name': 'test3',
@ -62,7 +62,7 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value3', 'value4']) self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value3', 'value4'])
def test_post_new_item2(self): 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', 'availability_policy': 'share',
'name': 'test3', 'name': 'test3',
'description': 'test', 'description': 'test',
@ -81,7 +81,7 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
self.assertEqual([p for p in item.properties.all()], []) self.assertEqual([p for p in item.properties.all()], [])
def test_post_new_item_empty(self): 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', 'availability_policy': 'rent',
'owned_quantity': 1, 'owned_quantity': 1,
'image': '', 'image': '',
@ -90,7 +90,7 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
self.assertEqual(InventoryItem.objects.count(), 2) self.assertEqual(InventoryItem.objects.count(), 2)
def test_post_new_item3(self): 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', 'availability_policy': 'private',
'name': 'test3', 'name': 'test3',
'description': 'test', 'description': 'test',
@ -110,7 +110,7 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
self.assertEqual([p for p in item.properties.all()], []) self.assertEqual([p for p in item.properties.all()], [])
def test_put_item(self): 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', 'availability_policy': 'sell',
'name': 'test4', 'name': 'test4',
'description': 'new description', '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']) self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value5', 'value6', 'value7'])
def test_patch_item(self): 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', 'description': 'new description2',
'category': 'cat1', 'category': 'cat1',
'owned_quantity': 100, 'owned_quantity': 100,
@ -153,7 +153,7 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value8']) self.assertEqual([p.value for p in item.itemproperty_set.all()], ['value8'])
def test_patch_item2(self): 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', 'description': 'new description2',
'category': None, 'category': None,
'owned_quantity': 100, 'owned_quantity': 100,
@ -172,14 +172,14 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
self.assertEqual([p for p in item.properties.all()], []) self.assertEqual([p for p in item.properties.all()], [])
def test_delete_item(self): 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(reply.status_code, 204)
self.assertEqual(InventoryItem.objects.count(), 1) self.assertEqual(InventoryItem.objects.count(), 1)
self.assertEqual(InventoryItem.objects.get(id=2).name, 'test2') self.assertEqual(InventoryItem.objects.get(id=2).name, 'test2')
self.assertEqual(InventoryItem.objects.filter(name='test1').count(), 0) self.assertEqual(InventoryItem.objects.filter(name='test1').count(), 0)
def test_search_items(self): 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 2) self.assertEqual(len(reply.json()), 2)
self.assertEqual(reply.json()[0]['name'], 'test1') self.assertEqual(reply.json()[0]['name'], 'test1')
@ -199,31 +199,31 @@ class InventoryApiTestCase(UserTestMixin, InventoryTestMixin, ToolshedTestCase):
self.assertEqual(reply.json()[1]['availability_policy'], 'friends') self.assertEqual(reply.json()[1]['availability_policy'], 'friends')
def test_search_items2(self): 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 2) self.assertEqual(len(reply.json()), 2)
self.assertEqual(reply.json()[0]['name'], 'test1') self.assertEqual(reply.json()[0]['name'], 'test1')
self.assertEqual(reply.json()[1]['name'], 'test2') self.assertEqual(reply.json()[1]['name'], 'test2')
def test_search_items_fail(self): 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.status_code, 400)
self.assertEqual(reply.json()['error'], 'No query provided.') self.assertEqual(reply.json()['error'], 'No query provided.')
def test_search_items_fail2(self): 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 0) self.assertEqual(len(reply.json()), 0)
def test_get_shared_item_as_friend(self): 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.f['local_user2'])
self.assertEqual(reply.status_code, 200) self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.json()['name'], 'test1') self.assertEqual(reply.json()['name'], 'test1')
self.assertEqual(reply.json()['owner'], 'testuser1@example.com') self.assertEqual(reply.json()['owner'], 'testuser1@example.com')
def test_get_shared_item_as_owner(self): 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.f['local_user1'])
self.assertEqual(reply.status_code, 200) self.assertEqual(reply.status_code, 200)
self.assertEqual(reply.json()['name'], 'test1') 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 # 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 # GroupOwnedInventoryApiTestCase.test_non_member_cannot_see_or_edit) - 404, not a
# separate 403 special case for "not a friend". # 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.f['ext_user1'])
self.assertEqual(reply.status_code, 404) self.assertEqual(reply.status_code, 404)
def test_get_shared_item_private(self): def test_get_shared_item_private(self):
private_item = InventoryItem.create_for_owner( private_item = InventoryItem.create_for_owner(
owner=self.f['local_user1'], owned_quantity=1, name='secret', availability_policy='private') 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.f['local_user2'])
self.assertEqual(reply.status_code, 404) 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.f['local_user1'])
self.assertEqual(reply.status_code, 200) self.assertEqual(reply.status_code, 200)
def test_get_shared_item_unknown_handle(self): 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.f['local_user2'])
self.assertEqual(reply.status_code, 404) self.assertEqual(reply.status_code, 404)
def test_get_shared_item_unknown_id(self): 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) self.assertEqual(reply.status_code, 404)
def test_get_shared_item_bad_handle(self): 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) self.assertEqual(reply.status_code, 400)
@ -272,7 +272,7 @@ class TestInventoryItemWithFileApiTestCase(UserTestMixin, FilesTestMixin, Invent
self.own_handle = str(self.f['local_user1']) self.own_handle = str(self.f['local_user1'])
def test_post_item_with_file_id(self): 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', 'name': 'test4',
'description': 'test', 'description': 'test',
'category': 'cat1', 'category': 'cat1',
@ -295,7 +295,7 @@ class TestInventoryItemWithFileApiTestCase(UserTestMixin, FilesTestMixin, Invent
self.assertEqual([f for f in item.files.all()], [self.f['test_file1']]) self.assertEqual([f for f in item.files.all()], [self.f['test_file1']])
def test_post_item_with_encoded_file(self): 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', 'name': 'test4',
'description': 'test', 'description': 'test',
'category': 'cat1', 'category': 'cat1',
@ -318,7 +318,7 @@ class TestInventoryItemWithFileApiTestCase(UserTestMixin, FilesTestMixin, Invent
self.assertEqual([f for f in item.files.all()], [self.f['test_file3']]) self.assertEqual([f for f in item.files.all()], [self.f['test_file3']])
def test_post_item_with_file_id_fail(self): 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', 'name': 'test4',
'description': 'test', 'description': 'test',
'category': 'cat1', 'category': 'cat1',
@ -330,7 +330,7 @@ class TestInventoryItemWithFileApiTestCase(UserTestMixin, FilesTestMixin, Invent
self.assertEqual(reply.status_code, 400) self.assertEqual(reply.status_code, 400)
def test_post_item_with_encoded_file_fail(self): 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', 'name': 'test4',
'description': 'test', 'description': 'test',
'category': 'cat1', 'category': 'cat1',
@ -357,7 +357,7 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest
self.group_handle = '+' + str(self.f['group1'])[1:] self.group_handle = '+' + str(self.f['group1'])[1:]
def create_group_item(self, name='drill'): 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', 'name': name, 'owned_quantity': 1, 'availability_policy': 'private',
}) })
@ -371,7 +371,7 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest
self.assertIsNone(reply.json()['owner']) self.assertIsNone(reply.json()['owner'])
def test_create_group_owned_item_non_member_denied(self): 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', 'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private',
}) })
self.assertEqual(reply.status_code, 403) self.assertEqual(reply.status_code, 403)
@ -379,7 +379,7 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest
def test_other_member_can_edit(self): def test_other_member_can_edit(self):
item_id = self.create_group_item().json()['id'] 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' 'name': 'drill-renamed'
}) })
self.assertEqual(reply.status_code, 200) self.assertEqual(reply.status_code, 200)
@ -387,20 +387,20 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest
def test_other_member_can_delete(self): def test_other_member_can_delete(self):
item_id = self.create_group_item().json()['id'] 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(reply.status_code, 204)
self.assertEqual(InventoryItem.objects.filter(id=item_id).count(), 0) self.assertEqual(InventoryItem.objects.filter(id=item_id).count(), 0)
def test_delete_group_item_when_id_collides_with_own_personal_item(self): def test_delete_group_item_when_id_collides_with_own_personal_item(self):
own_handle = str(self.f['local_user2']) 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', 'name': 'personal-drill', 'owned_quantity': 1, 'availability_policy': 'private',
}) })
self.assertEqual(personal_reply.json()['id'], 1) self.assertEqual(personal_reply.json()['id'], 1)
item_id = self.create_group_item().json()['id'] item_id = self.create_group_item().json()['id']
self.assertEqual(item_id, 1) 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(reply.status_code, 204)
self.assertEqual(InventoryItem.objects.filter(name='drill').count(), 0) 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. # items - not unauthorized just because .user.exists() is False.
self.f['group1'].members.add(self.f['ext_user1'].public_identity) self.f['group1'].members.add(self.f['ext_user1'].public_identity)
item_id = self.create_group_item().json()['id'] 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) 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' 'name': 'drill-renamed-by-remote-member'
}) })
self.assertEqual(reply.status_code, 200) self.assertEqual(reply.status_code, 200)
@ -421,32 +421,32 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest
def test_non_member_cannot_see_or_edit(self): def test_non_member_cannot_see_or_edit(self):
item_id = self.create_group_item().json()['id'] 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) self.assertEqual(reply.status_code, 404)
def test_group_items_excluded_from_personal_list(self): def test_group_items_excluded_from_personal_list(self):
self.create_group_item() 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 0) self.assertEqual(len(reply.json()), 0)
def test_group_items_listed_by_owner_handle(self): def test_group_items_listed_by_owner_handle(self):
self.create_group_item() 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 1) self.assertEqual(len(reply.json()), 1)
self.assertEqual(reply.json()[0]['name'], 'drill') self.assertEqual(reply.json()[0]['name'], 'drill')
def test_group_items_not_listed_for_non_member(self): def test_group_items_not_listed_for_non_member(self):
self.create_group_item() 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 0) self.assertEqual(len(reply.json()), 0)
def test_create_group_owned_item_with_full_fields(self): def test_create_group_owned_item_with_full_fields(self):
# Parity with InventoryApiTestCase.test_post_new_item: tags/properties/category attach # Parity with InventoryApiTestCase.test_post_new_item: tags/properties/category attach
# to a group-owned item the same way as a personal one. # 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', 'availability_policy': 'rent',
'category': 'cat2', 'category': 'cat2',
'name': 'drill', 'name': 'drill',
@ -468,21 +468,21 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest
def test_create_group_owned_item_empty_fails(self): 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. # 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, 'availability_policy': 'private', 'owned_quantity': 1,
}) })
self.assertEqual(reply.status_code, 400) self.assertEqual(reply.status_code, 400)
self.assertEqual(InventoryItem.objects.count(), 0) self.assertEqual(InventoryItem.objects.count(), 0)
def test_create_group_owned_item_nonexistent_group(self): 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', 'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private',
}) })
self.assertEqual(reply.status_code, 404) self.assertEqual(reply.status_code, 404)
self.assertEqual(InventoryItem.objects.count(), 0) self.assertEqual(InventoryItem.objects.count(), 0)
def test_group_items_listed_for_nonexistent_group(self): 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 0) 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 # 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. # the creator, to exercise the _is_authorized branch in perform_update for PUT too.
item_id = self.create_group_item().json()['id'] 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', 'availability_policy': 'sell',
'name': 'drill-4000', 'name': 'drill-4000',
'description': 'new description', 'description': 'new description',
@ -510,12 +510,12 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest
def test_patch_group_item_clears_fields(self): def test_patch_group_item_clears_fields(self):
# Parity with InventoryApiTestCase.test_patch_item2 - clearing category/tags/properties. # 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', 'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private',
'category': 'cat1', 'tags': ['tag1'], 'category': 'cat1', 'tags': ['tag1'],
}) })
item_id = reply.json()['id'] 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': [] 'category': None, 'tags': [], 'properties': []
}) })
self.assertEqual(reply.status_code, 200) self.assertEqual(reply.status_code, 200)
@ -524,7 +524,7 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest
self.assertEqual([t for t in item.tags.all()], []) self.assertEqual([t for t in item.tags.all()], [])
def test_group_item_storage_location(self): 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', 'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private',
'storage_location': self.f['loc1'].id, 'storage_location': self.f['loc1'].id,
}) })
@ -534,7 +534,7 @@ class GroupOwnedInventoryApiTestCase(UserTestMixin, GroupTestMixin, CategoryTest
def test_post_group_item_with_file_id(self): def test_post_group_item_with_file_id(self):
# Parity with TestInventoryItemWithFileApiTestCase.test_post_item_with_file_id. # 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', 'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private',
'files': [self.f['test_file1'].id], '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']]) self.assertEqual([f for f in item.files.all()], [self.f['test_file1']])
def test_post_group_item_with_encoded_file(self): 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', 'name': 'drill', 'owned_quantity': 1, 'availability_policy': 'private',
'files': [{'data': self.f['encoded_content3'], 'mime_type': 'text/plain'}], '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. # docs/design-in-progress/groups-mvp.md) - search must not surface them either.
self.create_group_item(name='searchable-drill') self.create_group_item(name='searchable-drill')
InventoryItem.create_for_owner(owner=self.f['local_user1'], owned_quantity=1, name='searchable-personal') 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) self.assertEqual(reply.status_code, 200)
names = [item['name'] for item in reply.json()] names = [item['name'] for item in reply.json()]
self.assertEqual(names, ['searchable-personal']) 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) self.assertEqual(self.f['loc1'], self.f['loc4'].parent)
def test_get_inventory(self): 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 2) self.assertEqual(len(reply.json()), 2)
self.assertEqual(reply.json()[0]['name'], 'test1') self.assertEqual(reply.json()[0]['name'], 'test1')
@ -51,7 +51,7 @@ class LocationApiTestCase(UserTestMixin, InventoryTestMixin, LocationTestMixin,
self.assertEqual(reply.json()[1]['availability_policy'], 'friends') self.assertEqual(reply.json()[1]['availability_policy'], 'friends')
def test_get_inventory_item(self): 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 4) self.assertEqual(len(reply.json()), 4)
self.assertEqual(reply.json()[0]['name'], 'loc1') self.assertEqual(reply.json()[0]['name'], 'loc1')
@ -72,7 +72,7 @@ class LocationApiTestCase(UserTestMixin, InventoryTestMixin, LocationTestMixin,
self.assertEqual(reply.json()[3]['path'], 'loc1/loc4') self.assertEqual(reply.json()[3]['path'], 'loc1/loc4')
def test_post_new_location(self): 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', 'name': 'loc5',
'description': 'a new location', 'description': 'a new location',
}) })
@ -85,7 +85,7 @@ class LocationApiTestCase(UserTestMixin, InventoryTestMixin, LocationTestMixin,
self.assertEqual(reply.json()['path'], 'loc5') self.assertEqual(reply.json()['path'], 'loc5')
def test_post_new_nested_location(self): 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', 'name': 'loc5',
'parent': self.f['loc3'].id, 'parent': self.f['loc3'].id,
}) })
@ -96,7 +96,7 @@ class LocationApiTestCase(UserTestMixin, InventoryTestMixin, LocationTestMixin,
def test_patch_location(self): def test_patch_location(self):
reply = client.patch( 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', 'name': 'loc2-renamed',
'parent': self.f['loc1'].id, 'parent': self.f['loc1'].id,
}) })
@ -108,7 +108,7 @@ class LocationApiTestCase(UserTestMixin, InventoryTestMixin, LocationTestMixin,
def test_delete_location(self): def test_delete_location(self):
reply = client.delete( 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(reply.status_code, 204)
self.assertEqual(StorageLocation.objects.count(), 3) self.assertEqual(StorageLocation.objects.count(), 3)
self.assertEqual(StorageLocation.objects.filter(id=self.f['loc4'].id).count(), 0) 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( item = InventoryItem.create_for_owner(
owner=self.f['local_user1'], name='located_item', storage_location=self.f['loc3']) owner=self.f['local_user1'], name='located_item', storage_location=self.f['loc3'])
reply = client.delete( 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) self.assertEqual(reply.status_code, 204)
item.refresh_from_db() item.refresh_from_db()
self.assertIsNone(item.storage_location) self.assertIsNone(item.storage_location)
self.assertEqual(InventoryItem.objects.filter(id=item.id).count(), 1) self.assertEqual(InventoryItem.objects.filter(id=item.id).count(), 1)
def test_locations_are_owner_scoped(self): 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 0) 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 # handle resolves and the location is visible - friends can read but never write, so this
# is a 403 (found, not authorized), not a 404. # is a 403 (found, not authorized), not a 404.
reply = client.delete( 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(reply.status_code, 403)
self.assertEqual(StorageLocation.objects.filter(id=self.f['loc1'].id).count(), 1) 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:] self.group_handle = '+' + str(self.f['group1'])[1:]
def create_group_location(self, name='shelf'): 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, 'name': name,
}) })
@ -163,7 +163,7 @@ class GroupOwnedLocationApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestC
self.assertIsNone(reply.json()['owner']) self.assertIsNone(reply.json()['owner'])
def test_create_group_owned_location_non_member_denied(self): 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', 'name': 'shelf',
}) })
self.assertEqual(reply.status_code, 403) self.assertEqual(reply.status_code, 403)
@ -172,7 +172,7 @@ class GroupOwnedLocationApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestC
def test_other_member_can_edit(self): def test_other_member_can_edit(self):
location_id = self.create_group_location().json()['id'] location_id = self.create_group_location().json()['id']
reply = client.patch( 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' 'name': 'shelf-renamed'
}) })
self.assertEqual(reply.status_code, 200) self.assertEqual(reply.status_code, 200)
@ -181,7 +181,7 @@ class GroupOwnedLocationApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestC
def test_other_member_can_delete(self): def test_other_member_can_delete(self):
location_id = self.create_group_location().json()['id'] location_id = self.create_group_location().json()['id']
reply = client.delete( 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(reply.status_code, 204)
self.assertEqual(StorageLocation.objects.filter(id=location_id).count(), 0) 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 # 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. # bare, unscoped id - means this can no longer raise MultipleObjectsReturned.
own_handle = str(self.f['local_user2']) 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'}) {'name': 'personal-shelf'})
self.assertEqual(personal_reply.json()['id'], 1) self.assertEqual(personal_reply.json()['id'], 1)
location_id = self.create_group_location().json()['id'] location_id = self.create_group_location().json()['id']
self.assertEqual(location_id, 1) self.assertEqual(location_id, 1)
reply = client.delete( 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(reply.status_code, 204)
self.assertEqual(StorageLocation.objects.filter(name='shelf').count(), 0) 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) self.f['group1'].members.add(self.f['ext_user1'].public_identity)
location_id = self.create_group_location().json()['id'] location_id = self.create_group_location().json()['id']
reply = client.get( 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) self.assertEqual(reply.status_code, 200)
reply = client.patch( 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' 'name': 'shelf-renamed-by-remote-member'
}) })
self.assertEqual(reply.status_code, 200) self.assertEqual(reply.status_code, 200)
@ -222,31 +222,31 @@ class GroupOwnedLocationApiTestCase(UserTestMixin, GroupTestMixin, ToolshedTestC
def test_non_member_cannot_see_or_edit(self): def test_non_member_cannot_see_or_edit(self):
location_id = self.create_group_location().json()['id'] location_id = self.create_group_location().json()['id']
reply = client.get( 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) self.assertEqual(reply.status_code, 404)
def test_group_locations_excluded_from_personal_list(self): def test_group_locations_excluded_from_personal_list(self):
self.create_group_location() 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 0) self.assertEqual(len(reply.json()), 0)
def test_group_locations_listed_by_owner_handle(self): def test_group_locations_listed_by_owner_handle(self):
self.create_group_location() 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 1) self.assertEqual(len(reply.json()), 1)
self.assertEqual(reply.json()[0]['name'], 'shelf') self.assertEqual(reply.json()[0]['name'], 'shelf')
def test_group_locations_not_listed_for_non_member(self): def test_group_locations_not_listed_for_non_member(self):
self.create_group_location() 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 0) self.assertEqual(len(reply.json()), 0)
def test_group_location_can_be_parent_for_group_member(self): def test_group_location_can_be_parent_for_group_member(self):
parent_id = self.create_group_location('shelf').json()['id'] 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, 'name': 'bin', 'parent': parent_id,
}) })
self.assertEqual(reply.status_code, 201) 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 # InventoryItem.storage_location already had before group ownership existed here; not
# something this feature narrows. # something this feature narrows.
personal = StorageLocation.create_for_owner(name='mine', owner=self.f['local_user1']) 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, 'name': 'bin', 'parent': personal.id,
}) })
self.assertEqual(reply.status_code, 201) 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): def test_group_location_not_valid_parent_for_non_member(self):
parent_id = self.create_group_location('shelf').json()['id'] parent_id = self.create_group_location('shelf').json()['id']
reply = client.post( 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, 'name': 'bin', 'parent': parent_id,
}) })
self.assertEqual(reply.status_code, 400) self.assertEqual(reply.status_code, 400)

View file

@ -47,11 +47,11 @@ class _DeleteTestDataMixin(UserTestMixin, CategoryTestMixin, LocationTestMixin):
class DeleteDataTestCase(_DeleteTestDataMixin, ToolshedTestCase): class DeleteDataTestCase(_DeleteTestDataMixin, ToolshedTestCase):
def test_delete_data_anonymous(self): 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) self.assertEqual(response.status_code, 403)
def test_delete_data_removes_all_owned_data_but_keeps_account(self): 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) self.assertEqual(response.status_code, 200)
summary = response.json() summary = response.json()
@ -83,14 +83,14 @@ class DeleteDataTestCase(_DeleteTestDataMixin, ToolshedTestCase):
class DeleteAccountTestCase(_DeleteTestDataMixin, ToolshedTestCase): class DeleteAccountTestCase(_DeleteTestDataMixin, ToolshedTestCase):
def test_delete_account_anonymous(self): 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) self.assertEqual(response.status_code, 403)
def test_delete_account_removes_data_and_closes_account(self): def test_delete_account_removes_data_and_closes_account(self):
user1_id = self.f['local_user1'].id user1_id = self.f['local_user1'].id
identity_id = self.f['local_user1'].public_identity_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) self.assertEqual(response.status_code, 200)
summary = response.json() summary = response.json()
@ -219,7 +219,7 @@ class ImportInventoryPropertiesTestCase(UserTestMixin, CategoryTestMixin, TagTes
class ExportImportApiRoundTripTestCase(UserTestMixin, CategoryTestMixin, TagTestMixin, PropertyTestMixin, class ExportImportApiRoundTripTestCase(UserTestMixin, CategoryTestMixin, TagTestMixin, PropertyTestMixin,
ToolshedTestCase): 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): def setUp(self):
super().setUp() 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['prop1'], value='10cm, 20cm')
ItemProperty.objects.create(inventory_item=item, property=self.f['prop2'], value='a=b') 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) self.assertEqual(export_reply.status_code, 200)
zip_bytes = export_reply.content 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')}) {'zip': base64.b64encode(zip_bytes).decode('ascii')})
self.assertEqual(import_reply.status_code, 200) self.assertEqual(import_reply.status_code, 200)
summary = import_reply.json() summary = import_reply.json()

View file

@ -41,7 +41,7 @@ class PropertyApiTestCase(PropertyTestMixin, CategoryTestMixin, UserTestMixin, T
self.prepare_properties() self.prepare_properties()
def test_get_properties(self): 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 3) self.assertEqual(len(reply.json()), 3)
self.assertEqual(reply.json()[0]['name'], 'prop1') self.assertEqual(reply.json()[0]['name'], 'prop1')

View file

@ -41,7 +41,7 @@ class TagApiTestCase(TagTestMixin, CategoryTestMixin, UserTestMixin, ToolshedTes
self.prepare_tags() self.prepare_tags()
def test_get_tags(self): 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(reply.status_code, 200)
self.assertEqual(len(reply.json()), 3) self.assertEqual(len(reply.json()), 3)
self.assertEqual(reply.json()[0], 'tag1') self.assertEqual(reply.json()[0], 'tag1')

View file

@ -20,7 +20,7 @@ class WorkflowInstanceApiTestCase(UserTestMixin, WorkflowTestMixin, ToolshedTest
def test_get_workflow_instances(self): 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(reply.status_code, status.HTTP_200_OK)
self.assertEqual(len(reply.data), 3) self.assertEqual(len(reply.data), 3)
self.assertEqual(reply.data[0]['name'], 'workflow1') self.assertEqual(reply.data[0]['name'], 'workflow1')
@ -39,7 +39,7 @@ class WorkflowInstanceApiTestCase(UserTestMixin, WorkflowTestMixin, ToolshedTest
def test_get_workflow_instances_user2(self): 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(reply.status_code, status.HTTP_200_OK)
self.assertEqual(len(reply.data), 1) self.assertEqual(len(reply.data), 1)
self.assertEqual(reply.data[0]['name'], 'workflow2') self.assertEqual(reply.data[0]['name'], 'workflow2')

View file

@ -8,9 +8,9 @@ from nacl.signing import SigningKey
from json import dumps, loads from json import dumps, loads
COMMANDS = { COMMANDS = {
'getinventory': {'path': '/api/inventory_items/{handle}/', 'method': 'get'}, 'getinventory': {'path': '/api/v1/inventory_items/{handle}/', 'method': 'get'},
'additem': {'path': '/api/inventory_items/{handle}/', 'method': 'post'}, 'additem': {'path': '/api/v1/inventory_items/{handle}/', 'method': 'post'},
'delitem': {'path': '/api/inventory_items/{handle}/{internal_id}/', 'method': 'delete'}, 'delitem': {'path': '/api/v1/inventory_items/{handle}/{internal_id}/', 'method': 'delete'},
} }
@ -110,7 +110,7 @@ class ToolshedApi:
def get_raw(self, target): def get_raw(self, target):
"""Like get(), but returns the raw response body instead of parsing it as JSON - for """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) response = self._send('GET', target)
if not response.ok: if not response.ok:
raise ApiError("{} {} from {}: {}".format( 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): def run_command(api, cmd, cmd_args, json_input):
if cmd == 'export': if cmd == 'export':
path = cmd_args[0] if cmd_args else 'toolshed-export.zip' 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: with open(path, 'wb') as f:
f.write(data) f.write(data)
return {'exported_to': path, 'bytes': len(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] path = cmd_args[0]
with open(path, 'rb') as f: with open(path, 'rb') as f:
data = f.read() 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) command = COMMANDS.get(cmd)
if command is None: if command is None:

View file

@ -579,7 +579,6 @@ export default {
this.importError = null; this.importError = null;
const created_item_ids = []; const created_item_ids = [];
const errors = []; 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()) { for (const [index, row] of this.items.entries()) {
if (!row.name) { if (!row.name) {
errors.push(`Row ${index + 1}: missing required "name" field, skipped.`); 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}) { async fetchInventoryItems({state, commit, dispatch, getters}) {
const servers = await dispatch('getHomeServers') 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)) items.map(item => item.files.map(file => file.owner = item.owner))
commit('setInventoryItems', {url: '/', items}) commit('setInventoryItems', {url: '/', items})
return items return items
@ -407,7 +407,7 @@ export default createStore({
const owner = item.owner_group ? encodeHandleForUrl(item.owner_group) : state.user const owner = item.owner_group ? encodeHandleForUrl(item.owner_group) : state.user
const data = {availability_policy: 'private', ...item} const data = {availability_policy: 'private', ...item}
delete data.owner_group 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 state.last_load.files = 0
return reply return reply
}, },
@ -418,28 +418,28 @@ export default createStore({
const data = {availability_policy: 'friends', ...item} const data = {availability_policy: 'friends', ...item}
data.files = data.files.map(file => file.id) 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. // 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) return await servers.patch(getters.signAuth, path, data)
}, },
async deleteInventoryItem({state, dispatch, getters}, item) { async deleteInventoryItem({state, dispatch, getters}, item) {
const servers = item.owner_group const servers = item.owner_group
? await dispatch('getFriendServers', {username: 'x@' + splitGroupHandle(item.owner_group).domain}) ? await dispatch('getFriendServers', {username: 'x@' + splitGroupHandle(item.owner_group).domain})
: await dispatch('getHomeServers') : 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) const ret = await servers.delete(getters.signAuth, path)
dispatch('fetchInventoryItems') dispatch('fetchInventoryItems')
return ret return ret
}, },
async fetchSearchResults({state, commit, dispatch, getters}, {query}) { async fetchSearchResults({state, commit, dispatch, getters}, {query}) {
const servers = await dispatch('getAllKnownServers') 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)) items.map(item => item.files.map(file => file.owner = item.owner))
commit('setInventoryItems', {url: '/search/' + query, items}) commit('setInventoryItems', {url: '/search/' + query, items})
return items return items
}, },
async fetchFriends({commit, dispatch, getters, state}) { async fetchFriends({commit, dispatch, getters, state}) {
const servers = await dispatch('getHomeServers') 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) commit('setFriends', data)
return data return data
}, },
@ -461,7 +461,7 @@ export default createStore({
try { try {
const servers = await dispatch('getFriendServers', {username: owner}); 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. // 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) { if (item && item.files) {
item.files.forEach(file => file.owner = item.owner) item.files.forEach(file => file.owner = item.owner)
} }
@ -485,14 +485,14 @@ export default createStore({
}, },
async fetchFriendRequests({state, dispatch, getters}) { async fetchFriendRequests({state, dispatch, getters}) {
const servers = await dispatch('getHomeServers') 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}) { async requestFriend({state, dispatch, getters}, {username}) {
if (username in state.friends) { if (username in state.friends) {
return true; return true;
} }
const home_servers = await dispatch('getHomeServers') 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, befriender: state.user,
befriendee: username befriendee: username
}) })
@ -500,7 +500,7 @@ export default createStore({
return false; return false;
const befriendee_servers = await dispatch('getFriendServers', {username}) 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, befriender: state.user,
befriendee: username, befriendee: username,
befriender_key: nacl.to_hex(state.keypair.signPk), befriender_key: nacl.to_hex(state.keypair.signPk),
@ -510,11 +510,11 @@ export default createStore({
}, },
async acceptFriend({state, dispatch, getters}, {id, secret, befriender}) { async acceptFriend({state, dispatch, getters}, {id, secret, befriender}) {
const home_servers = await dispatch('getHomeServers') 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 friend_request_id: id, secret: secret
}) })
const ext_servers = await dispatch('getFriendServers', {username: befriender}) 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, befriender: state.user,
befriendee: befriender, befriendee: befriender,
befriender_key: nacl.to_hex(state.keypair.signPk), befriender_key: nacl.to_hex(state.keypair.signPk),
@ -524,21 +524,21 @@ export default createStore({
}, },
async declineFriend({state, dispatch, getters}, {id}) { async declineFriend({state, dispatch, getters}, {id}) {
const servers = await dispatch('getHomeServers') 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}) { async dropFriend({state, dispatch, getters}, {id}) {
const servers = await dispatch('getHomeServers') 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}) { async fetchGroups({commit, dispatch, getters}) {
const servers = await dispatch('getHomeServers') 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) commit('setGroups', data)
return data return data
}, },
async fetchIdMap({commit, dispatch, getters}) { async fetchIdMap({commit, dispatch, getters}) {
const servers = await dispatch('getHomeServers') 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) commit('setIdMap', idmap)
return idmap return idmap
}, },
@ -546,7 +546,7 @@ export default createStore({
async resolveShortId({dispatch, getters}, {domain, kind, ownerId, localId}) { async resolveShortId({dispatch, getters}, {domain, kind, ownerId, localId}) {
try { try {
const servers = await dispatch('getFriendServers', {username: 'x@' + domain}) 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) { } catch (error) {
console.error(`Failed to resolve short id (${kind}, ${ownerId}, ${localId}) on ${domain}:`, error); console.error(`Failed to resolve short id (${kind}, ${ownerId}, ${localId}) on ${domain}:`, error);
return null; return null;
@ -556,27 +556,27 @@ export default createStore({
async fetchGroup({dispatch, getters}, {handle}) { async fetchGroup({dispatch, getters}, {handle}) {
const {domain} = splitGroupHandle(handle) const {domain} = splitGroupHandle(handle)
const servers = await dispatch('getFriendServers', {username: 'x@' + domain}) 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}) { async createGroup({dispatch, getters}, {name}) {
const servers = await dispatch('getHomeServers') 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}) { async removeGroupMember({dispatch, getters}, {groupHandle, identityId}) {
const {domain} = splitGroupHandle(groupHandle) const {domain} = splitGroupHandle(groupHandle)
const servers = await dispatch('getFriendServers', {username: 'x@' + domain}) const servers = await dispatch('getFriendServers', {username: 'x@' + domain})
return await servers.delete( 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}) { async fetchGroupInvites({commit, dispatch, getters}) {
const servers = await dispatch('getHomeServers') 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) commit('setGroupInvites', data)
return data return data
}, },
async fetchGroupMemberships({commit, dispatch, getters}) { async fetchGroupMemberships({commit, dispatch, getters}) {
const servers = await dispatch('getHomeServers') 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) commit('setGroupMemberships', data)
return data return data
}, },
@ -584,12 +584,12 @@ export default createStore({
const {domain} = splitGroupHandle(groupHandle) const {domain} = splitGroupHandle(groupHandle)
const group_servers = await dispatch('getFriendServers', {username: 'x@' + domain}) const group_servers = await dispatch('getFriendServers', {username: 'x@' + domain})
const group_reply = await group_servers.post( 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) { if (!group_reply.secret) {
return false return false
} }
const invitee_servers = await dispatch('getFriendServers', {username: invitee}) 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, group: groupHandle,
inviter: state.user, inviter: state.user,
inviter_key: nacl.to_hex(state.keypair.signPk), inviter_key: nacl.to_hex(state.keypair.signPk),
@ -601,24 +601,24 @@ export default createStore({
async acceptGroupInvite({state, dispatch, getters}, invite) { async acceptGroupInvite({state, dispatch, getters}, invite) {
const group_domain = invite.group.split('@')[1] const group_domain = invite.group.split('@')[1]
const group_servers = await dispatch('getFriendServers', {username: 'x@' + group_domain}) 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, group: invite.group,
invitee: state.user, invitee: state.user,
invitee_key: nacl.to_hex(state.keypair.signPk), invitee_key: nacl.to_hex(state.keypair.signPk),
secret: invite.secret secret: invite.secret
}) })
const home_servers = await dispatch('getHomeServers') 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 return true
}, },
async declineGroupInvite({dispatch, getters}, invite) { async declineGroupInvite({dispatch, getters}, invite) {
const servers = await dispatch('getHomeServers') 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}) { async fetchGroupInventoryItems({commit, dispatch, getters}, {groupHandle}) {
const {domain} = splitGroupHandle(groupHandle) const {domain} = splitGroupHandle(groupHandle)
const servers = await dispatch('getFriendServers', {username: 'x@' + domain}) 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 // 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 // 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. // 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 return state.files
} }
const servers = await dispatch('getHomeServers') 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) data.map(file => file.owner = state.user)
commit('setFiles', data) commit('setFiles', data)
state.last_load.files = Date.now() state.last_load.files = Date.now()
@ -638,11 +638,11 @@ export default createStore({
}, },
async fetchFilesByItem({state, commit, dispatch, getters}, {id}) { async fetchFilesByItem({state, commit, dispatch, getters}, {id}) {
const servers = await dispatch('getHomeServers') 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}) { async pushFile({state, dispatch, getters}, {item_id, file}) {
const servers = await dispatch('getHomeServers') 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) { if (data.name) {
data.owner = state.user data.owner = state.user
state.files.push(data) state.files.push(data)
@ -651,28 +651,28 @@ export default createStore({
}, },
async deleteFile({state, dispatch, getters}, {id}) { async deleteFile({state, dispatch, getters}, {id}) {
const servers = await dispatch('getHomeServers') 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) state.files = state.files.filter(file => file.id !== id)
}, },
async deleteItemFile({state, dispatch, getters}, {item_id, file_id}) { async deleteItemFile({state, dispatch, getters}, {item_id, file_id}) {
const servers = await dispatch('getHomeServers') 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) state.files = state.files.filter(file => file.id !== file_id)
}, },
async stageFile({state, dispatch, getters}, {lifetime_id, file}) { async stageFile({state, dispatch, getters}, {lifetime_id, file}) {
const servers = await dispatch('getHomeServers') 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) { if (data.hash) {
return data.hash return data.hash
} }
}, },
async unstageFile({state, dispatch, getters}, {lifetime_id, file_hash}) { async unstageFile({state, dispatch, getters}, {lifetime_id, file_hash}) {
const servers = await dispatch('getHomeServers') 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}) { async commitStagedFile({state, dispatch, getters}, {item_id, file_hash}) {
const servers = await dispatch('getHomeServers') 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) { if (data.name) {
data.owner = state.user data.owner = state.user
state.files.push(data) state.files.push(data)
@ -684,7 +684,7 @@ export default createStore({
return state.tags return state.tags
} }
const servers = await dispatch('getHomeServers') 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) commit('setTags', data)
state.last_load.tags = Date.now() state.last_load.tags = Date.now()
return data return data
@ -694,7 +694,7 @@ export default createStore({
return state.properties return state.properties
} }
const servers = await dispatch('getHomeServers') 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) commit('setProperties', data)
state.last_load.properties = Date.now() state.last_load.properties = Date.now()
return data return data
@ -704,7 +704,7 @@ export default createStore({
return state.categories return state.categories
} }
const servers = await dispatch('getHomeServers') 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) commit('setCategories', data)
state.last_load.categories = Date.now() state.last_load.categories = Date.now()
return data return data
@ -714,7 +714,7 @@ export default createStore({
return state.availability_policies return state.availability_policies
} }
const servers = await dispatch('getHomeServers') 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]}))) commit('setAvailabilityPolicies', data.map(policy => ({slug: policy[0], text: policy[1]})))
state.last_load.availability_policies = Date.now() state.last_load.availability_policies = Date.now()
return data return data
@ -724,7 +724,7 @@ export default createStore({
return state.storage_locations return state.storage_locations
} }
const servers = await dispatch('getHomeServers') 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) commit('setStorageLocations', data)
state.last_load.storage_locations = Date.now() state.last_load.storage_locations = Date.now()
return data return data
@ -733,7 +733,7 @@ export default createStore({
const {domain} = splitGroupHandle(groupHandle) const {domain} = splitGroupHandle(groupHandle)
const servers = await dispatch('getFriendServers', {username: 'x@' + domain}) const servers = await dispatch('getFriendServers', {username: 'x@' + domain})
const locations = await servers.get( 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}) commit('setLocationsForKey', {url: '/' + groupHandle, locations})
return locations return locations
}, },
@ -755,7 +755,7 @@ export default createStore({
? await dispatch('getFriendServers', {username: 'x@' + splitGroupHandle(location.owner_group).domain}) ? await dispatch('getFriendServers', {username: 'x@' + splitGroupHandle(location.owner_group).domain})
: await dispatch('getHomeServers') : 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. // 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) const ret = await servers.delete(getters.signAuth, path)
state.last_load.storage_locations = 0 state.last_load.storage_locations = 0
dispatch('fetchStorageLocations') dispatch('fetchStorageLocations')
@ -771,7 +771,7 @@ export default createStore({
const data = {...location} const data = {...location}
delete data.owner_group delete data.owner_group
if (data.parent === '') data.parent = null 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 state.last_load.storage_locations = 0
return reply return reply
}, },
@ -781,7 +781,7 @@ export default createStore({
: await dispatch('getHomeServers') : await dispatch('getHomeServers')
const data = {...location} const data = {...location}
if (data.parent === '') data.parent = null 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) const reply = await servers.patch(getters.signAuth, path, data)
state.last_load.storage_locations = 0 state.last_load.storage_locations = 0
dispatch('fetchStorageLocations') dispatch('fetchStorageLocations')
@ -797,7 +797,7 @@ export default createStore({
return state.info return state.info
} }
const servers = await dispatch('getHomeServers') 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('setTags', data.tags)
commit('setProperties', data.properties) commit('setProperties', data.properties)
commit('setCategories', data.categories) commit('setCategories', data.categories)
@ -820,7 +820,7 @@ export default createStore({
return state.active_workflows return state.active_workflows
} }
const servers = await dispatch('getHomeServers') 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) const workflows = data.map(deserializeWorkflowPayload)
commit('setActiveWorkflows', workflows) commit('setActiveWorkflows', workflows)
state.last_load.active_workflows = Date.now() state.last_load.active_workflows = Date.now()
@ -830,19 +830,19 @@ export default createStore({
const servers = await dispatch('getHomeServers') const servers = await dispatch('getHomeServers')
// The backend stores payload as an opaque string; the frontend (de)serializes it. // The backend stores payload as an opaque string; the frontend (de)serializes it.
// See docs/implementation.md#workflow-payload-is-an-opaque-string. // 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 state.last_load.active_workflows = 0 // Invalidate cache
return deserializeWorkflowPayload(data) return deserializeWorkflowPayload(data)
}, },
async updateWorkflow({state, commit, dispatch, getters}, workflow) { async updateWorkflow({state, commit, dispatch, getters}, workflow) {
const servers = await dispatch('getHomeServers') 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 state.last_load.active_workflows = 0 // Invalidate cache
return deserializeWorkflowPayload(data) return deserializeWorkflowPayload(data)
}, },
async updateWorkflowStep({state, commit, dispatch, getters}, {workflowId, currentStep, payload}) { async updateWorkflowStep({state, commit, dispatch, getters}, {workflowId, currentStep, payload}) {
const servers = await dispatch('getHomeServers') 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, current_step: currentStep,
payload: JSON.stringify(payload) payload: JSON.stringify(payload)
}) })
@ -851,7 +851,7 @@ export default createStore({
}, },
async deleteWorkflow({state, commit, dispatch, getters}, workflowId) { async deleteWorkflow({state, commit, dispatch, getters}, workflowId) {
const servers = await dispatch('getHomeServers') 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 state.last_load.active_workflows = 0 // Invalidate cache
return true return true
} }

View file

@ -152,7 +152,7 @@ export default {
const unreachable = new NeighborsCache(); const unreachable = new NeighborsCache();
const servers = await this.lookupServer({username: this.username}).then(servers => new ServerSet(servers, unreachable)) const servers = await this.lookupServer({username: this.username}).then(servers => new ServerSet(servers, unreachable))
const auth = createSignAuth(this.username, this.keypair.signSk); 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) { if (reply.status === 200) {
this.setUserFromIdentityRecord({username: this.username, keypair: this.keypair}); this.setUserFromIdentityRecord({username: this.username, keypair: this.keypair});
router.push({path: '/'}); router.push({path: '/'});

View file

@ -69,7 +69,7 @@ export default {
} }
try { try {
const servers = await this.getHomeServers(); 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) { if (!response || !response.ok) {
const errorBody = response ? await response.json().catch(() => ({})) : {}; const errorBody = response ? await response.json().catch(() => ({})) : {};
alert('Account deletion failed: ' + (errorBody.detail || response?.statusText || 'unknown error')); alert('Account deletion failed: ' + (errorBody.detail || response?.statusText || 'unknown error'));

View file

@ -102,7 +102,7 @@ export default {
} }
try { try {
const servers = await this.getHomeServers(); 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) { if (!response || !response.ok) {
const errorBody = response ? await response.json().catch(() => ({})) : {}; const errorBody = response ? await response.json().catch(() => ({})) : {};
alert('Data deletion failed: ' + (errorBody.detail || response?.statusText || 'unknown error')); alert('Data deletion failed: ' + (errorBody.detail || response?.statusText || 'unknown error'));
@ -148,7 +148,7 @@ export default {
try { try {
const base64 = await this.fileToBase64(this.selectedFile); const base64 = await this.fileToBase64(this.selectedFile);
const servers = await this.getHomeServers(); 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) { if (summary && summary.detail) {
alert('Data import failed: ' + summary.detail); alert('Data import failed: ' + summary.detail);
return; return;
@ -172,7 +172,7 @@ export default {
}, },
async exportData() { async exportData() {
const servers = await this.getHomeServers(); 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 blob = new Blob([await data.blob()], {type: 'application/zip'});
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');