Skip to content

Commit

Permalink
Add clone_repository celery task and clone-repository API endpoint
Browse files Browse the repository at this point in the history
- Add testing for clone_repository function synchronously;
- Add testing for clone-repository endpoint restrictions;
- Updates the "clone_version" celery task signature and it's only
reference;
  • Loading branch information
victor-salles committed Aug 29, 2022
1 parent 433dbb2 commit d6fbac2
Show file tree
Hide file tree
Showing 8 changed files with 430 additions and 64 deletions.
10 changes: 10 additions & 0 deletions bothub/api/v2/repository/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1672,3 +1672,13 @@ def to_representation(self, instance):
if data.get("organization"):
data.pop("organization")
return data


class RepositoryCloneSerializer(serializers.Serializer):

repository = serializers.PrimaryKeyRelatedField(
queryset=Repository.objects.all(), required=True
)
owner = serializers.PrimaryKeyRelatedField(
queryset=RepositoryOwner.objects.all(), required=True
)
20 changes: 20 additions & 0 deletions bothub/api/v2/repository/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
WordDistributionSerializer,
RemoveRepositoryProject,
AddRepositoryProjectSerializer,
RepositoryCloneSerializer,
)

from bothub.api.v2.internal.connect_rest_client import (
Expand Down Expand Up @@ -1069,6 +1070,8 @@ def destroy(self, request, *args, **kwargs):


class RepositoryTokenByUserViewSet(mixins.ListModelMixin, GenericViewSet):
serializer_class = RepositoryAuthorizationSerializer

def get_queryset(self):
user = self.request.user
if user.is_anonymous:
Expand Down Expand Up @@ -1497,3 +1500,20 @@ def get_serializer(self, *args, **kwargs):
kwargs["many"] = True

return super().get_serializer(*args, **kwargs)


class CloneRepositoryViewSet(mixins.CreateModelMixin, GenericViewSet):
queryset = Repository.objects.none()
serializer_class = RepositoryCloneSerializer
permission_classes = (IsAuthenticated,)

def create(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)

owner_id = serializer.data.get("owner")
repository_id = serializer.data.get("repository")
repository = Repository.objects.get(pk=repository_id)

slug, message, http_status = repository.clone_self(owner_id)
return Response(slug if slug else message, status=http_status)
109 changes: 57 additions & 52 deletions bothub/api/v2/routers.py
Original file line number Diff line number Diff line change
@@ -1,78 +1,81 @@
from rest_framework import routers

from bothub.api.v2.internal.organization.views import InternalOrganizationViewSet
from bothub.api.v2.internal.repository.views import InternalRepositoriesViewSet
from bothub.api.v2.internal.user.views import (
UserLanguageViewSet,
UserPermissionViewSet,
UserViewSet,
)
from bothub.api.v2.versionning.views import RepositoryVersionViewSet
from .account.views import ChangePasswordViewSet
from .account.views import LoginViewSet
from .account.views import MyUserProfileViewSet
from .account.views import RegisterUserViewSet
from .account.views import RequestResetPasswordViewSet
from .account.views import ResetPasswordViewSet
from .account.views import SearchUserViewSet
from .account.views import UserProfileViewSet
from .evaluate.views import EvaluateViewSet
from .evaluate.views import ResultsListViewSet

from .account.views import (
ChangePasswordViewSet,
LoginViewSet,
MyUserProfileViewSet,
RegisterUserViewSet,
RequestResetPasswordViewSet,
ResetPasswordViewSet,
SearchUserViewSet,
UserProfileViewSet,
)
from .evaluate.views import EvaluateViewSet, ResultsListViewSet
from .examples.views import ExamplesViewSet
from .groups.views import RepositoryEntityGroupViewSet
from .knowledge_base.views import QAKnowledgeBaseViewSet, QAtextViewSet
from .nlp.views import NLPLangsViewSet, RepositoryQANLPLogsViewSet
from .nlp.views import RepositoryAuthorizationEvaluateViewSet
from .nlp.views import RepositoryAuthorizationInfoViewSet
from .nlp.views import RepositoryAuthorizationParseViewSet
from .nlp.views import (
RepositoryAuthorizationTrainViewSet,
RepositoryNLPLogsViewSet,
RepositoryAuthorizationKnowledgeBaseViewSet,
RepositoryAuthorizationExamplesViewSet,
NLPLangsViewSet,
RepositoryAuthorizationAutomaticEvaluateViewSet,
RepositoryAuthorizationEvaluateViewSet,
RepositoryAuthorizationExamplesViewSet,
RepositoryAuthorizationInfoViewSet,
RepositoryAuthorizationKnowledgeBaseViewSet,
RepositoryAuthorizationParseViewSet,
RepositoryAuthorizationTrainLanguagesViewSet,
RepositoryAuthorizationTrainViewSet,
RepositoryNLPLogsViewSet,
RepositoryQANLPLogsViewSet,
RepositoryUpdateInterpretersViewSet,
)
from .nlp.views import RepositoryUpdateInterpretersViewSet
from .organization.views import (
OrganizationViewSet,
OrganizationProfileViewSet,
OrganizationAuthorizationViewSet,
OrganizationProfileViewSet,
OrganizationViewSet,
)
from .repository.views import (
RepositoriesContributionsViewSet,
RepositoryQANLPLogViewSet,
)
from .repository.views import RepositoriesViewSet
from .repository.views import RepositoryAuthorizationRequestsViewSet
from .repository.views import RepositoryAuthorizationViewSet
from .repository.views import RepositoryTokenByUserViewSet
from .repository.views import RepositoryCategoriesView
from .repository.views import RepositoryExampleViewSet
from .repository.views import RepositoryMigrateViewSet
from .repository.views import (
RepositoryViewSet,
RepositoryNLPLogViewSet,
RepositoryEntitiesViewSet,
CloneRepositoryViewSet,
NewRepositoryViewSet,
RasaUploadViewSet,
RepositoryTaskQueueViewSet,
RepositoriesContributionsViewSet,
RepositoriesPermissionsViewSet,
RepositoryNLPLogReportsViewSet,
RepositoriesViewSet,
RepositoryAuthorizationRequestsViewSet,
RepositoryAuthorizationViewSet,
RepositoryCategoriesView,
RepositoryEntitiesViewSet,
RepositoryExamplesBulkViewSet,
RepositoryExampleViewSet,
RepositoryIntentViewSet,
RepositoryTranslatorInfoViewSet,
RepositoryMigrateViewSet,
RepositoryNLPLogReportsViewSet,
RepositoryNLPLogViewSet,
RepositoryQANLPLogViewSet,
RepositoryTaskQueueViewSet,
RepositoryTokenByUserViewSet,
RepositoryTrainInfoViewSet,
RepositoryExamplesBulkViewSet,
RepositoryTranslatorInfoViewSet,
RepositoryViewSet,
RepositoryVotesViewSet,
SearchRepositoriesViewSet,
)
from .translation.views import (
RepositoryTranslatedExampleViewSet,
RepositoryTranslatedExporterViewSet,
)
from .repository.views import RepositoryVotesViewSet
from .repository.views import SearchRepositoriesViewSet
from .translation.views import RepositoryTranslatedExampleViewSet
from .translation.views import RepositoryTranslatedExporterViewSet
from .translator.views import (
TranslatorExamplesViewSet,
RepositoryTranslationTranslatorExampleViewSet,
RepositoryTranslatorViewSet,
)

from bothub.api.v2.internal.repository.views import InternalRepositoriesViewSet
from bothub.api.v2.internal.organization.views import InternalOrganizationViewSet
from bothub.api.v2.internal.user.views import (
UserPermissionViewSet,
UserViewSet,
UserLanguageViewSet,
TranslatorExamplesViewSet,
)


Expand Down Expand Up @@ -205,6 +208,8 @@ def get_lookup_regex(self, viewset, lookup_prefix=""):
router.register("repository/upload-rasa-file", RasaUploadViewSet)
router.register("repository/entity/group", RepositoryEntityGroupViewSet)
router.register("repository/repository-migrate", RepositoryMigrateViewSet)
router.register("repository/clone-repository", CloneRepositoryViewSet)

router.register(
"repository/nlp/authorization/train", RepositoryAuthorizationTrainViewSet
)
Expand Down
106 changes: 101 additions & 5 deletions bothub/api/v2/tests/test_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from bothub.api.v2.repository.serializers import NewRepositorySerializer
from bothub.api.v2.repository.views import (
CloneRepositoryViewSet,
RepositoriesContributionsViewSet,
RepositoryEntitiesViewSet,
NewRepositoryViewSet,
Expand All @@ -28,6 +29,7 @@
from bothub.api.v2.versionning.views import RepositoryVersionViewSet
from bothub.common import languages
from bothub.common.models import (
QAKnowledgeBase,
Repository,
Organization,
OrganizationAuthorization,
Expand Down Expand Up @@ -336,21 +338,29 @@ def test_authorization_permission_admin_in_organization(self):
role=RepositoryAuthorization.ROLE_ADMIN,
)

user, user_token = (self.owner, self.owner_token) if repository.is_private else (self.user, self.user_token)
user, user_token = (
(self.owner, self.owner_token)
if repository.is_private
else (self.user, self.user_token)
)
response, content_data = self.request(repository, user_token)
authorization = content_data.get("authorization")
self.assertIsNotNone(authorization)
self.assertTrue(authorization.get("can_read"))

# Assert owner access vs common user access behavior
if user is self.owner:
self.assertEqual(authorization.get("level"), OrganizationAuthorization.ROLE_ADMIN)
self.assertEqual(
authorization.get("level"), OrganizationAuthorization.ROLE_ADMIN
)
self.assertTrue(authorization.get("can_contribute"))
self.assertTrue(authorization.get("can_write"))
self.assertTrue(authorization.get("can_translate"))
self.assertTrue(authorization.get("is_admin"))
else: # is not owner
self.assertEqual(authorization.get("level"), OrganizationAuthorization.ROLE_USER)
self.assertEqual(
authorization.get("level"), OrganizationAuthorization.ROLE_USER
)
self.assertFalse(authorization.get("can_contribute"))
self.assertFalse(authorization.get("can_write"))
self.assertFalse(authorization.get("can_translate"))
Expand All @@ -363,14 +373,18 @@ def test_authorization_permission_admin_in_organization(self):
response, content_data = self.request(repository, user_token)
authorization = content_data.get("authorization")
self.assertIsNotNone(authorization)
self.assertEqual(authorization.get("level"), OrganizationAuthorization.ROLE_USER)
self.assertEqual(
authorization.get("level"), OrganizationAuthorization.ROLE_USER
)

# User should have role==ROLE_USER when they lose their authorization in the repository
repository_authorization.delete()
response, content_data = self.request(repository, user_token)
authorization = content_data.get("authorization")
self.assertIsNotNone(authorization)
self.assertEqual(authorization.get("level"), OrganizationAuthorization.ROLE_USER)
self.assertEqual(
authorization.get("level"), OrganizationAuthorization.ROLE_USER
)


class RepositoryAvailableRequestAuthorizationTestCase(TestCase):
Expand Down Expand Up @@ -2411,3 +2425,85 @@ def test_can_evaluate_automatic(self):
response, content_data = self.request(self.repository, data, self.owner_token)
self.assertTrue(content_data.get("can_run_evaluate_automatic"))
self.assertEqual(len(content_data.get("messages")), 0)


class RepositoryCloneTestCase(TestCase):
"""Test repository_clone API and validate the underlying function, validating all repository fields
and making sure that new fields, such as foreignkeys and many-to-many relationships will cause test failure,
requiring for clone function refactor."""

def setUp(self) -> None:
self.factory = RequestFactory()
self.owner, self.owner_token = create_user_and_token("owner")
self.user, self.user_token = create_user_and_token("user")
self.organization = Organization.objects.create(name="Org", verificated=True)
self.organization.set_user_permission(
self.user, OrganizationAuthorization.ROLE_ADMIN
)

# Create categories for repositories
self.category_1 = RepositoryCategory.objects.create(name="Category 1")
self.category_2 = RepositoryCategory.objects.create(name="Category 2")
self.repositories = [
create_repository_from_mockup(self.owner, **mockup)
for mockup in get_valid_mockups([self.category_1, self.category_2])
]
# Create versions and knowledge bases for repositories
for repository in self.repositories:
RepositoryVersion.objects.create(
repository=repository, name="beta", is_default=False
)
RepositoryVersion.objects.create(
repository=repository, name="alfa", is_default=True
)

QAKnowledgeBase.objects.create(repository=repository, user=self.user)
QAKnowledgeBase.objects.create(repository=repository, user=self.owner)

return super().setUp()

def request(self, data, token=""):
authorization_header = (
{"HTTP_AUTHORIZATION": "Token {}".format(token.key)} if token else {}
)

url = "/v2/repository/clone-repository/"
request = self.factory.post(url, data, **authorization_header)

response = CloneRepositoryViewSet.as_view({"post": "create"})(request)
response.render()
content_data = json.loads(response.content)
return (response, content_data)

def test_cannot_clone_private_repository(self):
repository = self.repositories[1]
repository.is_private = True
repository.save()
response, content_data = self.request(
{"repository": repository.pk, "owner": self.organization.pk},
self.owner_token,
)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

def test_cannot_call_api_while_unauthenticated(self):
"""Validate that only authenticated users can clone repositories with an unauthenticated request"""
repository = self.repositories[1]
response, content_data = self.request(
{"repository": repository.pk, "owner": self.organization.pk}, token=None
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

def test_cannot_call_api_with_empty_or_incomplete_body(self):
repository = self.repositories[0]
response, content_data = self.request({}, token=self.user_token)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

response, content_data = self.request(
{"repository": repository.pk}, token=self.user_token
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

response, content_data = self.request(
{"owner": self.organization.pk}, token=self.user_token
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
Loading

0 comments on commit d6fbac2

Please sign in to comment.