Skip to content

Commit

Permalink
Add update repository mutation (#573)
Browse files Browse the repository at this point in the history
  • Loading branch information
rohitvinnakota-codecov authored Jun 4, 2024
1 parent a3a3716 commit 56dbd5d
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import pytest
from asgiref.sync import async_to_sync
from django.contrib.auth.models import AnonymousUser
from django.test import TransactionTestCase

from codecov.commands.exceptions import Unauthorized
from codecov_auth.tests.factories import OwnerFactory
from core.tests.factories import RepositoryFactory, RepositoryTokenFactory

from ..update_repository import UpdateRepositoryInteractor


class UpdateRepositoryInteractorTest(TransactionTestCase):
def setUp(self):
self.owner = OwnerFactory(username="codecov")
self.random_user = OwnerFactory(organizations=[])

def execute_unauthorized_owner(self):
return UpdateRepositoryInteractor(self.owner, "github").execute(
repo_name="repo-1",
owner=self.random_user,
default_branch=None,
activated=None,
)

async def test_when_validation_error_unauthorized_owner(self):
with pytest.raises(Unauthorized):
await self.execute_unauthorized_owner()
45 changes: 45 additions & 0 deletions core/commands/repository/interactors/update_repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from typing import Optional

from django.conf import settings

import services.self_hosted as self_hosted
from codecov.commands.base import BaseInteractor
from codecov.commands.exceptions import Unauthenticated, Unauthorized, ValidationError
from codecov.db import sync_to_async
from codecov_auth.helpers import current_user_part_of_org
from codecov_auth.models import Owner
from core.models import Repository


class UpdateRepositoryInteractor(BaseInteractor):
def validate_owner(self, owner: Owner):
if not self.current_user.is_authenticated:
raise Unauthenticated()

if not current_user_part_of_org(self.current_owner, owner):
raise Unauthorized()

@sync_to_async
def execute(
self,
repo_name: str,
owner: Owner,
default_branch: Optional[str],
activated: Optional[bool],
):
self.validate_owner(owner)
repo = Repository.objects.filter(author_id=owner.pk, name=repo_name).first()
if not repo:
raise ValidationError("Repo not found")

if default_branch:
branch = repo.branches.filter(name=default_branch).first()
if branch is None:
raise ValidationError(
f"The branch '{default_branch}' is not in our records. Please provide a valid branch name.",
)

repo.branch = default_branch
if activated:
repo.activated = activated
repo.save()
14 changes: 14 additions & 0 deletions core/commands/repository/repository.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Optional

from codecov.commands.base import BaseCommand
from codecov_auth.models import Owner
from core.models import Repository
Expand All @@ -9,12 +11,24 @@
from .interactors.get_repository_token import GetRepositoryTokenInteractor
from .interactors.get_upload_token import GetUploadTokenInteractor
from .interactors.regenerate_repository_token import RegenerateRepositoryTokenInteractor
from .interactors.update_repository import UpdateRepositoryInteractor


class RepositoryCommands(BaseCommand):
def fetch_repository(self, owner, name):
return self.get_interactor(FetchRepositoryInteractor).execute(owner, name)

def update_repository(
self,
repo_name: str,
owner: Owner,
default_branch: Optional[str],
activated: Optional[bool],
):
return self.get_interactor(UpdateRepositoryInteractor).execute(
repo_name, owner, default_branch, activated
)

def get_upload_token(self, repository):
return self.get_interactor(GetUploadTokenInteractor).execute(repository)

Expand Down
106 changes: 106 additions & 0 deletions graphql_api/tests/mutation/test_update_repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from django.test import TransactionTestCase, override_settings

from codecov_auth.tests.factories import OwnerFactory
from core.tests.factories import BranchFactory, RepositoryFactory
from graphql_api.tests.helper import GraphQLTestHelper

query = """
mutation($input: UpdateRepositoryInput!) {
updateRepository(input: $input) {
error {
__typename
... on ResolverError {
message
}
}
}
}
"""

repo_query = """{
me {
owner {
repository(name: "gazebo") {
... on Repository {
activated
defaultBranch
}
}
}
}
}
"""


class UpdateRepositoryTests(GraphQLTestHelper, TransactionTestCase):
def setUp(self):
self.org = OwnerFactory(username="codecov", service="github")
self.repo = RepositoryFactory(author=self.org, name="gazebo", activated=False)

def test_when_authenticated_update_activated(self):
data = self.gql_request(
query,
owner=self.org,
variables={"input": {"activated": True, "repoName": "gazebo"}},
)

repo_result = self.gql_request(
repo_query,
owner=self.org,
)
assert repo_result["me"]["owner"]["repository"]["activated"] == True

assert data == {"updateRepository": None}

def test_when_authenticated_update_branch(self):
other_branch = BranchFactory.create(
name="some other branch", repository=self.repo
)
data = self.gql_request(
query,
owner=self.org,
variables={"input": {"branch": "some other branch", "repoName": "gazebo"}},
)

repo_result = self.gql_request(
repo_query,
owner=self.org,
)
assert (
repo_result["me"]["owner"]["repository"]["defaultBranch"]
== "some other branch"
)

assert data == {"updateRepository": None}

def test_when_authenticated_branch_does_not_exist(self):
data = self.gql_request(
query,
owner=self.org,
variables={"input": {"branch": "Dne", "repoName": "gazebo"}},
)

assert data["updateRepository"]["error"]["__typename"] == "ValidationError"

def test_when_unauthenticated(self):
data = self.gql_request(
query,
variables={
"input": {
"repoName": "gazebo",
}
},
)
assert data["updateRepository"]["error"]["__typename"] == "UnauthenticatedError"

def test_when_validation_error_repo_not_found(self):
data = self.gql_request(
query,
owner=self.org,
variables={
"input": {
"repoName": "DNE",
}
},
)
assert data["updateRepository"]["error"]["__typename"] == "ValidationError"
2 changes: 2 additions & 0 deletions graphql_api/types/mutation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from .sync_with_git_provider import gql_sync_with_git_provider
from .update_default_organization import gql_update_default_organization
from .update_profile import gql_update_profile
from .update_repository import gql_update_repository
from .update_self_hosted_settings import gql_update_self_hosted_settings

mutation = ariadne_load_local_graphql(__file__, "mutation.graphql")
Expand All @@ -40,4 +41,5 @@
mutation = mutation + gql_start_trial
mutation = mutation + gql_cancel_trial
mutation = mutation + gql_delete_component_measurements
mutation = mutation + gql_update_repository
mutation = mutation + gql_update_self_hosted_settings
1 change: 1 addition & 0 deletions graphql_api/types/mutation/mutation.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ type Mutation {
saveSentryState(input: SaveSentryStateInput!): SaveSentryStatePayload
saveTermsAgreement(input: SaveTermsAgreementInput!): SaveTermsAgreementPayload
deleteComponentMeasurements(input: DeleteComponentMeasurementsInput!): DeleteComponentMeasurementsPayload
updateRepository(input: UpdateRepositoryInput!): UpdateRepositoryPayload
updateSelfHostedSettings(input: UpdateSelfHostedSettingsInput!): UpdateSelfHostedSettingsPayload
}
3 changes: 3 additions & 0 deletions graphql_api/types/mutation/mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
resolve_update_default_organization,
)
from .update_profile import error_update_profile, resolve_update_profile
from .update_repository import error_update_repository, resolve_update_repository
from .update_self_hosted_settings import (
error_update_self_hosted_settings,
resolve_update_self_hosted_settings,
Expand Down Expand Up @@ -71,6 +72,7 @@
mutation_bindable.field("deleteComponentMeasurements")(
resolve_delete_component_measurements
)
mutation_bindable.field("updateRepository")(resolve_update_repository)
mutation_bindable.field("updateSelfHostedSettings")(resolve_update_self_hosted_settings)


Expand All @@ -94,5 +96,6 @@
error_save_terms_agreement,
error_start_trial,
error_cancel_trial,
error_update_repository,
error_update_self_hosted_settings,
]
7 changes: 7 additions & 0 deletions graphql_api/types/mutation/update_repository/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from graphql_api.helpers.ariadne import ariadne_load_local_graphql

from .update_repository import error_update_repository, resolve_update_repository

gql_update_repository = ariadne_load_local_graphql(
__file__, "update_repository.graphql"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
union UpdateRepositoryError = UnauthenticatedError | ValidationError | UnauthorizedError

type UpdateRepositoryPayload {
error: UpdateRepositoryError
}

input UpdateRepositoryInput {
branch: String
activated: Boolean
repoName: String!
}
25 changes: 25 additions & 0 deletions graphql_api/types/mutation/update_repository/update_repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from ariadne import UnionType

from graphql_api.helpers.mutation import (
resolve_union_error_type,
wrap_error_handling_mutation,
)


@wrap_error_handling_mutation
async def resolve_update_repository(_, info, input):
command = info.context["executor"].get_command("repository")
owner = info.context["request"].current_owner
repo_name = input.get("repoName")
default_branch = input.get("branch")
activated = input.get("activated")
await command.update_repository(
repo_name,
owner,
default_branch,
activated,
)


error_update_repository = UnionType("UpdateRepositoryError")
error_update_repository.type_resolver(resolve_union_error_type)

0 comments on commit 56dbd5d

Please sign in to comment.