Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit e3a2467

Browse files
authored
Bundle Analysis: Add mutation for toggling bundle caching configuration (#1084)
1 parent a0c8267 commit e3a2467

File tree

11 files changed

+328
-1
lines changed

11 files changed

+328
-1
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import pytest
2+
from asgiref.sync import async_to_sync
3+
from django.test import TransactionTestCase
4+
from shared.django_apps.bundle_analysis.models import CacheConfig
5+
from shared.django_apps.core.tests.factories import (
6+
OwnerFactory,
7+
RepositoryFactory,
8+
)
9+
10+
from codecov.commands.exceptions import ValidationError
11+
12+
from ..update_bundle_cache_config import UpdateBundleCacheConfigInteractor
13+
14+
15+
class UpdateBundleCacheConfigInteractorTest(TransactionTestCase):
16+
databases = {"default"}
17+
18+
def setUp(self):
19+
self.org = OwnerFactory(username="test-org")
20+
self.repo = RepositoryFactory(author=self.org, name="test-repo", active=True)
21+
self.user = OwnerFactory(permission=[self.repo.pk])
22+
23+
@async_to_sync
24+
def execute(self, owner, repo_name=None, cache_config=[]):
25+
return UpdateBundleCacheConfigInteractor(owner, "github").execute(
26+
repo_name=repo_name,
27+
owner_username="test-org",
28+
cache_config=cache_config,
29+
)
30+
31+
def test_repo_not_found(self):
32+
with pytest.raises(ValidationError):
33+
self.execute(owner=self.user, repo_name="wrong")
34+
35+
def test_bundle_not_found(self):
36+
with pytest.raises(
37+
ValidationError, match="The following bundle names do not exist: wrong"
38+
):
39+
self.execute(
40+
owner=self.user,
41+
repo_name="test-repo",
42+
cache_config=[{"bundle_name": "wrong", "toggle_caching": True}],
43+
)
44+
45+
def test_some_bundles_not_found(self):
46+
CacheConfig.objects.create(
47+
repo_id=self.repo.pk, bundle_name="bundle1", is_caching=True
48+
)
49+
with pytest.raises(
50+
ValidationError, match="The following bundle names do not exist: bundle2"
51+
):
52+
self.execute(
53+
owner=self.user,
54+
repo_name="test-repo",
55+
cache_config=[
56+
{"bundle_name": "bundle1", "toggle_caching": False},
57+
{"bundle_name": "bundle2", "toggle_caching": True},
58+
],
59+
)
60+
61+
def test_update_bundles_successfully(self):
62+
CacheConfig.objects.create(
63+
repo_id=self.repo.pk, bundle_name="bundle1", is_caching=True
64+
)
65+
CacheConfig.objects.create(
66+
repo_id=self.repo.pk, bundle_name="bundle2", is_caching=True
67+
)
68+
69+
res = self.execute(
70+
owner=self.user,
71+
repo_name="test-repo",
72+
cache_config=[
73+
{"bundle_name": "bundle1", "toggle_caching": False},
74+
{"bundle_name": "bundle2", "toggle_caching": True},
75+
],
76+
)
77+
78+
assert res == [
79+
{"bundle_name": "bundle1", "is_cached": False},
80+
{"bundle_name": "bundle2", "is_cached": True},
81+
]
82+
83+
assert len(CacheConfig.objects.all()) == 2
84+
85+
query = CacheConfig.objects.filter(repo_id=self.repo.pk, bundle_name="bundle1")
86+
assert len(query) == 1
87+
assert query[0].is_caching == False
88+
89+
query = CacheConfig.objects.filter(repo_id=self.repo.pk, bundle_name="bundle2")
90+
assert len(query) == 1
91+
assert query[0].is_caching == True
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from typing import Dict, List
2+
3+
from shared.django_apps.bundle_analysis.models import CacheConfig
4+
from shared.django_apps.bundle_analysis.service.bundle_analysis import (
5+
BundleAnalysisCacheConfigService,
6+
)
7+
8+
from codecov.commands.base import BaseInteractor
9+
from codecov.commands.exceptions import ValidationError
10+
from codecov.db import sync_to_async
11+
from codecov_auth.models import Owner
12+
from core.models import Repository
13+
14+
15+
class UpdateBundleCacheConfigInteractor(BaseInteractor):
16+
def validate(
17+
self, repo: Repository, cache_config: List[Dict[str, str | bool]]
18+
) -> None:
19+
if not repo:
20+
raise ValidationError("Repo not found")
21+
22+
# Find any missing bundle names
23+
bundle_names = [
24+
bundle["bundle_name"]
25+
for bundle in cache_config
26+
# the value of bundle_name is always a string, just do this check to appease mypy
27+
if isinstance(bundle["bundle_name"], str)
28+
]
29+
existing_bundle_names = set(
30+
CacheConfig.objects.filter(
31+
repo_id=repo.pk, bundle_name__in=bundle_names
32+
).values_list("bundle_name", flat=True)
33+
)
34+
missing_bundles = set(bundle_names) - existing_bundle_names
35+
if missing_bundles:
36+
raise ValidationError(
37+
f"The following bundle names do not exist: {', '.join(missing_bundles)}"
38+
)
39+
40+
@sync_to_async
41+
def execute(
42+
self,
43+
owner_username: str,
44+
repo_name: str,
45+
cache_config: List[Dict[str, str | bool]],
46+
) -> List[Dict[str, str | bool]]:
47+
author = Owner.objects.filter(
48+
username=owner_username, service=self.service
49+
).first()
50+
repo = (
51+
Repository.objects.viewable_repos(self.current_owner)
52+
.filter(author=author, name=repo_name)
53+
.first()
54+
)
55+
56+
self.validate(repo, cache_config)
57+
58+
results = []
59+
for bundle in cache_config:
60+
bundle_name = bundle["bundle_name"]
61+
is_caching = bundle["toggle_caching"]
62+
BundleAnalysisCacheConfigService.update_cache_option(
63+
repo.pk, bundle_name, is_caching
64+
)
65+
results.append(
66+
{
67+
"bundle_name": bundle_name,
68+
"is_cached": is_caching,
69+
}
70+
)
71+
return results

core/commands/repository/repository.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import uuid
2-
from typing import Awaitable, Optional
2+
from typing import Awaitable, Dict, List, Optional
33

44
from codecov.commands.base import BaseCommand
55
from codecov_auth.models import Owner, RepositoryToken
@@ -16,6 +16,7 @@
1616
from .interactors.regenerate_repository_upload_token import (
1717
RegenerateRepositoryUploadTokenInteractor,
1818
)
19+
from .interactors.update_bundle_cache_config import UpdateBundleCacheConfigInteractor
1920
from .interactors.update_repository import UpdateRepositoryInteractor
2021

2122

@@ -85,3 +86,13 @@ def encode_secret_string(self, owner: Owner, repo_name: str, value: str) -> str:
8586
return self.get_interactor(EncodeSecretStringInteractor).execute(
8687
owner, repo_name, value
8788
)
89+
90+
def update_bundle_cache_config(
91+
self,
92+
owner_username: str,
93+
repo_name: str,
94+
cache_config: List[Dict[str, str | bool]],
95+
) -> Awaitable[List[Dict[str, str | bool]]]:
96+
return self.get_interactor(UpdateBundleCacheConfigInteractor).execute(
97+
owner_username, repo_name, cache_config
98+
)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from unittest.mock import patch
2+
3+
from django.test import TransactionTestCase
4+
from shared.django_apps.core.tests.factories import OwnerFactory
5+
6+
from graphql_api.tests.helper import GraphQLTestHelper
7+
8+
query = """
9+
mutation($input: ActivateMeasurementsInput!) {
10+
activateMeasurements(input: $input) {
11+
error {
12+
__typename
13+
}
14+
}
15+
}
16+
"""
17+
18+
19+
query = """
20+
mutation UpdateBundleCacheConfig(
21+
$owner: String!
22+
$repoName: String!
23+
$bundles: [BundleCacheConfigInput!]!
24+
) {
25+
updateBundleCacheConfig(input: {
26+
owner: $owner,
27+
repoName: $repoName,
28+
bundles: $bundles
29+
}) {
30+
results {
31+
bundleName
32+
isCached
33+
}
34+
error {
35+
__typename
36+
... on UnauthenticatedError {
37+
message
38+
}
39+
... on ValidationError {
40+
message
41+
}
42+
}
43+
}
44+
}
45+
"""
46+
47+
48+
class UpdateBundleCacheConfigTestCase(GraphQLTestHelper, TransactionTestCase):
49+
def setUp(self):
50+
self.owner = OwnerFactory()
51+
52+
def test_when_unauthenticated(self):
53+
data = self.gql_request(
54+
query,
55+
variables={
56+
"owner": "codecov",
57+
"repoName": "test-repo",
58+
"bundles": [{"bundleName": "pr_bundle1", "toggleCaching": True}],
59+
},
60+
)
61+
assert (
62+
data["updateBundleCacheConfig"]["error"]["__typename"]
63+
== "UnauthenticatedError"
64+
)
65+
66+
@patch(
67+
"core.commands.repository.interactors.update_bundle_cache_config.UpdateBundleCacheConfigInteractor.execute"
68+
)
69+
def test_when_authenticated(self, execute):
70+
data = self.gql_request(
71+
query,
72+
owner=self.owner,
73+
variables={
74+
"owner": "codecov",
75+
"repoName": "test-repo",
76+
"bundles": [{"bundleName": "pr_bundle1", "toggleCaching": True}],
77+
},
78+
)
79+
assert data == {"updateBundleCacheConfig": {"results": [], "error": None}}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
input BundleCacheConfigInput {
2+
bundleName: String!
3+
toggleCaching: Boolean!
4+
}
5+
6+
input UpdateBundleCacheConfigInput {
7+
owner: String!
8+
repoName: String!
9+
bundles: [BundleCacheConfigInput!]!
10+
}

graphql_api/types/mutation/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from .start_trial import gql_start_trial
2424
from .store_event_metrics import gql_store_event_metrics
2525
from .sync_with_git_provider import gql_sync_with_git_provider
26+
from .update_bundle_cache_config import gql_update_bundle_cache_config
2627
from .update_default_organization import gql_update_default_organization
2728
from .update_profile import gql_update_profile
2829
from .update_repository import gql_update_repository
@@ -55,3 +56,4 @@
5556
mutation = mutation + gql_store_event_metrics
5657
mutation = mutation + gql_save_okta_config
5758
mutation = mutation + gql_set_upload_token_required
59+
mutation = mutation + gql_update_bundle_cache_config

graphql_api/types/mutation/mutation.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,5 @@ type Mutation {
3939
storeEventMetric(input: StoreEventMetricsInput!): StoreEventMetricsPayload
4040
saveOktaConfig(input: SaveOktaConfigInput!): SaveOktaConfigPayload
4141
setUploadTokenRequired(input: SetUploadTokenRequiredInput!): SetUploadTokenRequiredPayload
42+
updateBundleCacheConfig(input: UpdateBundleCacheConfigInput!): UpdateBundleCacheConfigPayload
4243
}

graphql_api/types/mutation/mutation.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
error_sync_with_git_provider,
5050
resolve_sync_with_git_provider,
5151
)
52+
from .update_bundle_cache_config import (
53+
error_update_bundle_cache_config,
54+
resolve_update_bundle_cache_config,
55+
)
5256
from .update_default_organization import (
5357
error_update_default_organization,
5458
resolve_update_default_organization,
@@ -99,6 +103,7 @@
99103

100104
mutation_bindable.field("saveOktaConfig")(resolve_save_okta_config)
101105
mutation_bindable.field("setUploadTokenRequired")(resolve_set_upload_token_required)
106+
mutation_bindable.field("updateBundleCacheConfig")(resolve_update_bundle_cache_config)
102107

103108
mutation_resolvers = [
104109
mutation_bindable,
@@ -128,4 +133,5 @@
128133
error_store_event_metrics,
129134
error_save_okta_config,
130135
error_set_upload_token_required,
136+
error_update_bundle_cache_config,
131137
]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from graphql_api.helpers.ariadne import ariadne_load_local_graphql
2+
3+
from .update_bundle_cache_config import (
4+
error_update_bundle_cache_config,
5+
resolve_update_bundle_cache_config,
6+
)
7+
8+
gql_update_bundle_cache_config = ariadne_load_local_graphql(
9+
__file__, "update_bundle_cache_config.graphql"
10+
)
11+
12+
__all__ = [
13+
"error_update_bundle_cache_config",
14+
"resolve_update_bundle_cache_config",
15+
]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
union UpdateBundleCacheConfigError = UnauthenticatedError | ValidationError
2+
3+
type UpdateBundleCacheConfigResult {
4+
bundleName: String
5+
isCached: Boolean
6+
}
7+
8+
type UpdateBundleCacheConfigPayload {
9+
results: [UpdateBundleCacheConfigResult!]
10+
error: UpdateBundleCacheConfigError
11+
}

0 commit comments

Comments
 (0)