Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import pytest
from asgiref.sync import async_to_sync
from django.test import TransactionTestCase
from shared.django_apps.bundle_analysis.models import CacheConfig
from shared.django_apps.core.tests.factories import (
OwnerFactory,
RepositoryFactory,
)

from codecov.commands.exceptions import ValidationError

from ..update_bundle_cache_config import UpdateBundleCacheConfigInteractor


class UpdateBundleCacheConfigInteractorTest(TransactionTestCase):
databases = {"default"}

def setUp(self):
self.org = OwnerFactory(username="test-org")
self.repo = RepositoryFactory(author=self.org, name="test-repo", active=True)
self.user = OwnerFactory(permission=[self.repo.pk])

@async_to_sync
def execute(self, owner, repo_name=None, cache_config=[]):
return UpdateBundleCacheConfigInteractor(owner, "github").execute(
repo_name=repo_name,
owner_username="test-org",
cache_config=cache_config,
)

def test_repo_not_found(self):
with pytest.raises(ValidationError):
self.execute(owner=self.user, repo_name="wrong")

def test_bundle_not_found(self):
with pytest.raises(
ValidationError, match="The following bundle names do not exist: wrong"
):
self.execute(
owner=self.user,
repo_name="test-repo",
cache_config=[{"bundle_name": "wrong", "toggle_caching": True}],
)

def test_some_bundles_not_found(self):
CacheConfig.objects.create(
repo_id=self.repo.pk, bundle_name="bundle1", is_caching=True
)
with pytest.raises(
ValidationError, match="The following bundle names do not exist: bundle2"
):
self.execute(
owner=self.user,
repo_name="test-repo",
cache_config=[
{"bundle_name": "bundle1", "toggle_caching": False},
{"bundle_name": "bundle2", "toggle_caching": True},
],
)

def test_update_bundles_successfully(self):
CacheConfig.objects.create(
repo_id=self.repo.pk, bundle_name="bundle1", is_caching=True
)
CacheConfig.objects.create(
repo_id=self.repo.pk, bundle_name="bundle2", is_caching=True
)

res = self.execute(
owner=self.user,
repo_name="test-repo",
cache_config=[
{"bundle_name": "bundle1", "toggle_caching": False},
{"bundle_name": "bundle2", "toggle_caching": True},
],
)

assert res == [
{"bundle_name": "bundle1", "is_cached": False},
{"bundle_name": "bundle2", "is_cached": True},
]

assert len(CacheConfig.objects.all()) == 2

query = CacheConfig.objects.filter(repo_id=self.repo.pk, bundle_name="bundle1")
assert len(query) == 1
assert query[0].is_caching == False

query = CacheConfig.objects.filter(repo_id=self.repo.pk, bundle_name="bundle2")
assert len(query) == 1
assert query[0].is_caching == True
71 changes: 71 additions & 0 deletions core/commands/repository/interactors/update_bundle_cache_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from typing import Dict, List

from shared.django_apps.bundle_analysis.models import CacheConfig
from shared.django_apps.bundle_analysis.service.bundle_analysis import (
BundleAnalysisCacheConfigService,
)

from codecov.commands.base import BaseInteractor
from codecov.commands.exceptions import ValidationError
from codecov.db import sync_to_async
from codecov_auth.models import Owner
from core.models import Repository


class UpdateBundleCacheConfigInteractor(BaseInteractor):
def validate(
self, repo: Repository, cache_config: List[Dict[str, str | bool]]
) -> None:
if not repo:
raise ValidationError("Repo not found")

# Find any missing bundle names
bundle_names = [
bundle["bundle_name"]
for bundle in cache_config
# the value of bundle_name is always a string, just do this check to appease mypy
if isinstance(bundle["bundle_name"], str)
]
existing_bundle_names = set(
CacheConfig.objects.filter(
repo_id=repo.pk, bundle_name__in=bundle_names
).values_list("bundle_name", flat=True)
)
missing_bundles = set(bundle_names) - existing_bundle_names
if missing_bundles:
raise ValidationError(
f"The following bundle names do not exist: {', '.join(missing_bundles)}"
)

@sync_to_async
def execute(
self,
owner_username: str,
repo_name: str,
cache_config: List[Dict[str, str | bool]],
) -> List[Dict[str, str | bool]]:
author = Owner.objects.filter(
username=owner_username, service=self.service
).first()
repo = (
Repository.objects.viewable_repos(self.current_owner)
.filter(author=author, name=repo_name)
.first()
)

self.validate(repo, cache_config)

results = []
for bundle in cache_config:
bundle_name = bundle["bundle_name"]
is_caching = bundle["toggle_caching"]
BundleAnalysisCacheConfigService.update_cache_option(
repo.pk, bundle_name, is_caching
)
results.append(
{
"bundle_name": bundle_name,
"is_cached": is_caching,
}
)
return results
13 changes: 12 additions & 1 deletion core/commands/repository/repository.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import uuid
from typing import Awaitable, Optional
from typing import Awaitable, Dict, List, Optional

from codecov.commands.base import BaseCommand
from codecov_auth.models import Owner, RepositoryToken
Expand All @@ -16,6 +16,7 @@
from .interactors.regenerate_repository_upload_token import (
RegenerateRepositoryUploadTokenInteractor,
)
from .interactors.update_bundle_cache_config import UpdateBundleCacheConfigInteractor
from .interactors.update_repository import UpdateRepositoryInteractor


Expand Down Expand Up @@ -85,3 +86,13 @@ def encode_secret_string(self, owner: Owner, repo_name: str, value: str) -> str:
return self.get_interactor(EncodeSecretStringInteractor).execute(
owner, repo_name, value
)

def update_bundle_cache_config(
self,
owner_username: str,
repo_name: str,
cache_config: List[Dict[str, str | bool]],
) -> Awaitable[List[Dict[str, str | bool]]]:
return self.get_interactor(UpdateBundleCacheConfigInteractor).execute(
owner_username, repo_name, cache_config
)
79 changes: 79 additions & 0 deletions graphql_api/tests/mutation/test_update_bundle_cache_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from unittest.mock import patch

from django.test import TransactionTestCase
from shared.django_apps.core.tests.factories import OwnerFactory

from graphql_api.tests.helper import GraphQLTestHelper

query = """
mutation($input: ActivateMeasurementsInput!) {
activateMeasurements(input: $input) {
error {
__typename
}
}
}
"""


query = """
mutation UpdateBundleCacheConfig(
$owner: String!
$repoName: String!
$bundles: [BundleCacheConfigInput!]!
) {
updateBundleCacheConfig(input: {
owner: $owner,
repoName: $repoName,
bundles: $bundles
}) {
results {
bundleName
isCached
}
error {
__typename
... on UnauthenticatedError {
message
}
... on ValidationError {
message
}
}
}
}
"""


class UpdateBundleCacheConfigTestCase(GraphQLTestHelper, TransactionTestCase):
def setUp(self):
self.owner = OwnerFactory()

def test_when_unauthenticated(self):
data = self.gql_request(
query,
variables={
"owner": "codecov",
"repoName": "test-repo",
"bundles": [{"bundleName": "pr_bundle1", "toggleCaching": True}],
},
)
assert (
data["updateBundleCacheConfig"]["error"]["__typename"]
== "UnauthenticatedError"
)

@patch(
"core.commands.repository.interactors.update_bundle_cache_config.UpdateBundleCacheConfigInteractor.execute"
)
def test_when_authenticated(self, execute):
data = self.gql_request(
query,
owner=self.owner,
variables={
"owner": "codecov",
"repoName": "test-repo",
"bundles": [{"bundleName": "pr_bundle1", "toggleCaching": True}],
},
)
assert data == {"updateBundleCacheConfig": {"results": [], "error": None}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this return the bundles that were updated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nah this test is just to test the mutation, the UpdateBundleCacheConfigInteractor is being mocked, and that is being tested in the other test class.

10 changes: 10 additions & 0 deletions graphql_api/types/inputs/bundle_analysis_cache_config.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
input BundleCacheConfigInput {
bundleName: String!
toggleCaching: Boolean!
}

input UpdateBundleCacheConfigInput {
owner: String!
repoName: String!
bundles: [BundleCacheConfigInput!]!
}
2 changes: 2 additions & 0 deletions graphql_api/types/mutation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from .start_trial import gql_start_trial
from .store_event_metrics import gql_store_event_metrics
from .sync_with_git_provider import gql_sync_with_git_provider
from .update_bundle_cache_config import gql_update_bundle_cache_config
from .update_default_organization import gql_update_default_organization
from .update_profile import gql_update_profile
from .update_repository import gql_update_repository
Expand Down Expand Up @@ -55,3 +56,4 @@
mutation = mutation + gql_store_event_metrics
mutation = mutation + gql_save_okta_config
mutation = mutation + gql_set_upload_token_required
mutation = mutation + gql_update_bundle_cache_config
1 change: 1 addition & 0 deletions graphql_api/types/mutation/mutation.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ type Mutation {
storeEventMetric(input: StoreEventMetricsInput!): StoreEventMetricsPayload
saveOktaConfig(input: SaveOktaConfigInput!): SaveOktaConfigPayload
setUploadTokenRequired(input: SetUploadTokenRequiredInput!): SetUploadTokenRequiredPayload
updateBundleCacheConfig(input: UpdateBundleCacheConfigInput!): UpdateBundleCacheConfigPayload
}
6 changes: 6 additions & 0 deletions graphql_api/types/mutation/mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
error_sync_with_git_provider,
resolve_sync_with_git_provider,
)
from .update_bundle_cache_config import (
error_update_bundle_cache_config,
resolve_update_bundle_cache_config,
)
from .update_default_organization import (
error_update_default_organization,
resolve_update_default_organization,
Expand Down Expand Up @@ -99,6 +103,7 @@

mutation_bindable.field("saveOktaConfig")(resolve_save_okta_config)
mutation_bindable.field("setUploadTokenRequired")(resolve_set_upload_token_required)
mutation_bindable.field("updateBundleCacheConfig")(resolve_update_bundle_cache_config)

mutation_resolvers = [
mutation_bindable,
Expand Down Expand Up @@ -128,4 +133,5 @@
error_store_event_metrics,
error_save_okta_config,
error_set_upload_token_required,
error_update_bundle_cache_config,
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from graphql_api.helpers.ariadne import ariadne_load_local_graphql

from .update_bundle_cache_config import (
error_update_bundle_cache_config,
resolve_update_bundle_cache_config,
)

gql_update_bundle_cache_config = ariadne_load_local_graphql(
__file__, "update_bundle_cache_config.graphql"
)

__all__ = [
"error_update_bundle_cache_config",
"resolve_update_bundle_cache_config",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
union UpdateBundleCacheConfigError = UnauthenticatedError | ValidationError

type UpdateBundleCacheConfigResult {
bundleName: String
isCached: Boolean
}

type UpdateBundleCacheConfigPayload {
results: [UpdateBundleCacheConfigResult!]
error: UpdateBundleCacheConfigError
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from typing import Any, Dict, List

from ariadne import UnionType
from graphql import GraphQLResolveInfo

from core.commands.repository.repository import RepositoryCommands
from graphql_api.helpers.mutation import (
require_authenticated,
resolve_union_error_type,
wrap_error_handling_mutation,
)


@wrap_error_handling_mutation
@require_authenticated
async def resolve_update_bundle_cache_config(
_: Any, info: GraphQLResolveInfo, input: Dict[str, Any]
) -> Dict[str, List[Dict[str, str | bool]]]:
command: RepositoryCommands = info.context["executor"].get_command("repository")

results = await command.update_bundle_cache_config(
repo_name=input.get("repo_name", ""),
owner_username=input.get("owner", ""),
cache_config=input.get("bundles", []),
)
return {"results": results}


error_update_bundle_cache_config = UnionType("UpdateBundleCacheConfigError")
error_update_bundle_cache_config.type_resolver(resolve_union_error_type)
Loading