Skip to content

Commit ba5d7f9

Browse files
committed
somewhat working unit test and serializer. Need to clean up again after we add codecovClient convenience func
1 parent 114963a commit ba5d7f9

File tree

3 files changed

+185
-76
lines changed

3 files changed

+185
-76
lines changed
Lines changed: 128 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import sentry_sdk
2+
from rest_framework import serializers
13
from rest_framework.request import Request
24
from rest_framework.response import Response
35

@@ -62,14 +64,114 @@
6264
}
6365
"""
6466

65-
# NOTE: There is no testResult resolver in GQL atm so if we need this, will need to build it.
66-
get_query = """query GetTestResult(
67-
$owner: String!
68-
$repo: String!
69-
$testResultId: String!
70-
) {
67+
68+
class TestResultNodeSerializer(serializers.Serializer):
69+
"""
70+
Serializer for individual test result nodes from GraphQL response
71+
"""
72+
73+
updatedAt = serializers.CharField()
74+
avgDuration = serializers.FloatField()
75+
name = serializers.CharField()
76+
failureRate = serializers.FloatField()
77+
flakeRate = serializers.FloatField()
78+
commitsFailed = serializers.IntegerField()
79+
totalFailCount = serializers.IntegerField()
80+
totalFlakyFailCount = serializers.IntegerField()
81+
totalSkipCount = serializers.IntegerField()
82+
totalPassCount = serializers.IntegerField()
83+
84+
85+
class TestResultSerializer(serializers.Serializer):
86+
"""
87+
Serializer to transform GraphQL response to client format
88+
"""
89+
90+
def transform_graphql_response(self, graphql_response):
91+
"""
92+
Transform the GraphQL response format to the expected client format
93+
"""
94+
try:
95+
# Extract test result nodes from the nested GraphQL structure
96+
test_results = graphql_response["data"]["owner"]["repository"]["testAnalytics"][
97+
"testResults"
98+
]["edges"]
99+
100+
# Transform each node to the expected client format
101+
transformed_results = []
102+
for edge in test_results:
103+
node = edge["node"]
104+
# Note: lastDuration is not in the GraphQL response, using avgDuration as fallback
105+
transformed_result = {
106+
"updatedAt": node["updatedAt"],
107+
"name": node["name"],
108+
"commitsFailed": node["commitsFailed"],
109+
"failureRate": node["failureRate"],
110+
"flakeRate": node["flakeRate"],
111+
"avgDuration": node["avgDuration"],
112+
"lastDuration": node.get(
113+
"lastDuration", node["avgDuration"]
114+
), # fallback to avgDuration
115+
"totalFailCount": node["totalFailCount"],
116+
"totalFlakyFailCount": node["totalFlakyFailCount"],
117+
"totalSkipCount": node["totalSkipCount"],
118+
"totalPassCount": node["totalPassCount"],
119+
}
120+
transformed_results.append(transformed_result)
121+
122+
return transformed_results
123+
124+
except (KeyError, TypeError) as e:
125+
# Handle malformed GraphQL response
126+
sentry_sdk.capture_exception(e)
127+
128+
return []
129+
130+
131+
# Sample GraphQL response structure for reference
132+
sample_graphql_response = {
133+
"data": {
134+
"owner": {
135+
"repository": {
136+
"__typename": "Repository",
137+
"testAnalytics": {
138+
"testResults": {
139+
"edges": [
140+
{
141+
"node": {
142+
"updatedAt": "2025-05-22T16:21:18.763951+00:00",
143+
"avgDuration": 0.04066228070175437,
144+
"name": "../usr/local/lib/python3.13/site-packages/asgiref/sync.py::GetFinalYamlInteractorTest::test_when_commit_has_no_yaml",
145+
"failureRate": 0.0,
146+
"flakeRate": 0.0,
147+
"commitsFailed": 0,
148+
"totalFailCount": 0,
149+
"totalFlakyFailCount": 0,
150+
"totalSkipCount": 0,
151+
"totalPassCount": 70,
152+
}
153+
},
154+
{
155+
"node": {
156+
"updatedAt": "2025-05-22T16:21:18.763961+00:00",
157+
"avgDuration": 0.034125877192982455,
158+
"name": "../usr/local/lib/python3.13/site-packages/asgiref/sync.py::GetFinalYamlInteractorTest::test_when_commit_has_yaml",
159+
"failureRate": 0.0,
160+
"flakeRate": 0.0,
161+
"commitsFailed": 0,
162+
"totalFailCount": 0,
163+
"totalFlakyFailCount": 0,
164+
"totalSkipCount": 0,
165+
"totalPassCount": 70,
166+
}
167+
},
168+
],
169+
}
170+
},
171+
}
172+
}
173+
}
71174
}
72-
"""
73175

74176

75177
@region_silo_endpoint
@@ -79,79 +181,30 @@ class TestResultsEndpoint(CodecovEndpoint):
79181
"GET": ApiPublishStatus.PUBLIC,
80182
}
81183

82-
def get(self, request: Request) -> Response:
184+
# Disable pagination requirement for this endpoint
185+
def has_pagination(self, response):
186+
return True
187+
188+
def get(self, request: Request, owner: str, repository: str, commit: str) -> Response:
83189
"""Retrieves the list of test results for a given commit. If a test result id is also
84190
provided, the endpoint will return the test result with that id."""
85191

86-
test_result_id = request.GET.get("test_result_id")
87-
owner = request.GET.get("owner")
88-
repo = request.GET.get("repo")
89-
commit = request.GET.get("commit")
90-
91192
variables = {
92193
"owner": owner,
93-
"repo": repo,
194+
"repo": repository,
94195
"commit": commit,
95196
}
96197

97-
assert variables # just to get rid of lint error
98-
99-
if test_result_id:
100-
# TODO: graphQL Query
101-
# Passing in query into codecov client means we need the query to be structured by the time we call it.
102-
103-
# res = CodecovClient.query(get_query, variables)
104-
# transformed_res = CodecovClient.transform_response(res, serializer)
105-
106-
return Response(
107-
{
108-
"updatedAt": "2021-01-01T00:00:00Z",
109-
"name": "test",
110-
"commitsFailed": 1,
111-
"failureRate": 0.01,
112-
"flakeRate": 100,
113-
"avgDuration": 100,
114-
"lastDuration": 100,
115-
"totalFailCount": 1,
116-
"totalFlakyFailCount": 1,
117-
"totalSkipCount": 0,
118-
"totalPassCount": 0,
119-
}
120-
)
121-
122-
# CodecovClient.query(list_query, variables)
123-
124-
# TODO: Response filtering
125-
126-
return Response(
127-
{
128-
[
129-
{
130-
"updatedAt": "2021-01-01T00:00:00Z",
131-
"name": "test",
132-
"commitsFailed": 1,
133-
"failureRate": 0.01,
134-
"flakeRate": 100,
135-
"avgDuration": 100,
136-
"lastDuration": 100,
137-
"totalFailCount": 1,
138-
"totalFlakyFailCount": 1,
139-
"totalSkipCount": 0,
140-
"totalPassCount": 0,
141-
},
142-
{
143-
"updatedAt": "2021-01-01T00:00:00Z",
144-
"name": "test",
145-
"commitsFailed": 4,
146-
"failureRate": 0.5,
147-
"flakeRate": 0.2,
148-
"avgDuration": 100,
149-
"lastDuration": 100,
150-
"totalFailCount": 4,
151-
"totalFlakyFailCount": 0,
152-
"totalSkipCount": 0,
153-
"totalPassCount": 0,
154-
},
155-
]
156-
}
157-
)
198+
assert variables
199+
200+
# TODO: Uncomment when CodecovClient is available
201+
# graphql_response = CodecovClient.query(list_query, variables)
202+
203+
# For now, use the sample response for demonstration
204+
graphql_response = sample_graphql_response
205+
206+
# Transform the GraphQL response to client format
207+
serializer = TestResultSerializer()
208+
test_results = serializer.transform_graphql_response(graphql_response)
209+
210+
return Response(test_results)

src/sentry/api/urls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3189,7 +3189,7 @@ def create_group_urls(name_prefix: str) -> list[URLPattern | URLResolver]:
31893189

31903190
PREVENT_URLS = [
31913191
re_path(
3192-
r"^owner/(?P<owner>[^\/]+)/repository/(?P<repository>[^\/]+)/commit/(?P<commit>[^\/]+)/test-results/(?P<test_result_id>[^\/]+)$",
3192+
r"^owner/(?P<owner>[^\/]+)/repository/(?P<repository>[^\/]+)/commit/(?P<commit>[^\/]+)/test-results/$",
31933193
TestResultsEndpoint.as_view(),
31943194
name="sentry-api-0-test-results",
31953195
),
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from unittest.mock import patch
2+
3+
from django.urls import reverse
4+
5+
from sentry.testutils.cases import APITestCase
6+
7+
8+
class TestResultsEndpointTest(APITestCase):
9+
endpoint = "sentry-api-0-test-results"
10+
11+
def setUp(self):
12+
super().setUp()
13+
self.login_as(user=self.user)
14+
15+
def reverse_url(self, owner="testowner", repository="testrepo", commit="testcommit"):
16+
"""Custom reverse URL method to handle required URL parameters"""
17+
return reverse(
18+
self.endpoint,
19+
kwargs={
20+
"owner": owner,
21+
"repository": repository,
22+
"commit": commit,
23+
},
24+
)
25+
26+
@patch("sentry.api.endpoints.test_results.TestResultsEndpoint.permission_classes", ())
27+
def test_get_returns_mock_response(self):
28+
"""Test that GET request returns the expected mock GraphQL response structure"""
29+
url = self.reverse_url()
30+
response = self.client.get(url)
31+
32+
# With permissions bypassed, we should get a 200 response
33+
assert response.status_code == 200
34+
35+
# Validate the response structure
36+
assert isinstance(response.data, list)
37+
# The sample response should contain 2 test result items
38+
assert len(response.data) == 2
39+
40+
# Verify the first result has expected fields and values
41+
first_result = response.data[0]
42+
expected_fields = [
43+
"updatedAt",
44+
"name",
45+
"avgDuration",
46+
"failureRate",
47+
"flakeRate",
48+
"commitsFailed",
49+
"totalFailCount",
50+
"totalFlakyFailCount",
51+
"totalSkipCount",
52+
"totalPassCount",
53+
"lastDuration",
54+
]
55+
for field in expected_fields:
56+
assert field in first_result, f"Missing field: {field}"

0 commit comments

Comments
 (0)