Skip to content
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
Expand Up @@ -52,6 +52,12 @@ class BuildDetailsVcsInfo(BaseModel):
pr_number: int | None = None


class SizeInfoSizeMetric(BaseModel):
metrics_artifact_type: PreprodArtifactSizeMetrics.MetricsArtifactType
install_size_bytes: int
download_size_bytes: int


class SizeInfoPending(BaseModel):
state: Literal[PreprodArtifactSizeMetrics.SizeAnalysisState.PENDING] = (
PreprodArtifactSizeMetrics.SizeAnalysisState.PENDING
Expand All @@ -68,8 +74,11 @@ class SizeInfoCompleted(BaseModel):
state: Literal[PreprodArtifactSizeMetrics.SizeAnalysisState.COMPLETED] = (
PreprodArtifactSizeMetrics.SizeAnalysisState.COMPLETED
)
# Deprecated, use size_metrics instead
install_size_bytes: int
# Deprecated, use size_metrics instead
download_size_bytes: int
size_metrics: list[SizeInfoSizeMetric]


class SizeInfoFailed(BaseModel):
Expand Down Expand Up @@ -106,44 +115,65 @@ def platform_from_artifact_type(artifact_type: PreprodArtifact.ArtifactType) ->
raise ValueError(f"Unknown artifact type: {artifact_type}")


def to_size_info(size_metrics: None | PreprodArtifactSizeMetrics) -> None | SizeInfo:
if size_metrics is None:
def to_size_info(size_metrics: list[PreprodArtifactSizeMetrics]) -> None | SizeInfo:
if len(size_metrics) == 0:
return None
match size_metrics.state:

main_metric = next(
(
metric
for metric in size_metrics
if metric.metrics_artifact_type
== PreprodArtifactSizeMetrics.MetricsArtifactType.MAIN_ARTIFACT
),
# Fallback to the first metric if no main artifact is found
size_metrics[0],
)

match main_metric.state:
case PreprodArtifactSizeMetrics.SizeAnalysisState.PENDING:
return SizeInfoPending()
case PreprodArtifactSizeMetrics.SizeAnalysisState.PROCESSING:
return SizeInfoProcessing()
case PreprodArtifactSizeMetrics.SizeAnalysisState.COMPLETED:
max_install_size = size_metrics.max_install_size
max_download_size = size_metrics.max_download_size
max_install_size = main_metric.max_install_size
max_download_size = main_metric.max_download_size
if max_install_size is None or max_download_size is None:
raise ValueError(
"COMPLETED state requires both max_install_size and max_download_size"
)

return SizeInfoCompleted(
install_size_bytes=max_install_size, download_size_bytes=max_download_size
install_size_bytes=max_install_size,
download_size_bytes=max_download_size,
size_metrics=[
SizeInfoSizeMetric(
metrics_artifact_type=metric.metrics_artifact_type,
install_size_bytes=metric.max_install_size,
download_size_bytes=metric.max_download_size,
)
for metric in size_metrics
],
)
case PreprodArtifactSizeMetrics.SizeAnalysisState.FAILED:
error_code = size_metrics.error_code
error_message = size_metrics.error_message
error_code = main_metric.error_code
error_message = main_metric.error_message
if error_code is None or error_message is None:
raise ValueError("FAILED state requires both error_code and error_message")
return SizeInfoFailed(error_code=error_code, error_message=error_message)
case _:
raise ValueError(f"Unknown SizeAnalysisState {size_metrics.state}")
raise ValueError(f"Unknown SizeAnalysisState {main_metric.state}")


def transform_preprod_artifact_to_build_details(
artifact: PreprodArtifact,
) -> BuildDetailsApiResponse:

size_metrics = PreprodArtifactSizeMetrics.objects.filter(
size_metrics_qs = PreprodArtifactSizeMetrics.objects.filter(
preprod_artifact=artifact,
metrics_artifact_type=PreprodArtifactSizeMetrics.MetricsArtifactType.MAIN_ARTIFACT,
).first()
)

size_info = to_size_info(size_metrics)
size_info = to_size_info(list(size_metrics_qs))

platform = None
# artifact_type can be null before preprocessing has completed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ def test_get_comparison_success_with_no_matching_base_metric(self):
metrics_artifact_type=PreprodArtifactSizeMetrics.MetricsArtifactType.WATCH_ARTIFACT,
identifier="watch",
state=PreprodArtifactSizeMetrics.SizeAnalysisState.COMPLETED,
max_install_size=500,
max_download_size=250,
)

response = self.get_success_response(
Expand Down Expand Up @@ -395,6 +397,8 @@ def test_get_comparison_multiple_metrics(self):
metrics_artifact_type=PreprodArtifactSizeMetrics.MetricsArtifactType.WATCH_ARTIFACT,
identifier="watch",
state=PreprodArtifactSizeMetrics.SizeAnalysisState.COMPLETED,
max_install_size=500,
max_download_size=250,
)

base_watch_metric = PreprodArtifactSizeMetrics.objects.create(
Expand All @@ -403,6 +407,8 @@ def test_get_comparison_multiple_metrics(self):
metrics_artifact_type=PreprodArtifactSizeMetrics.MetricsArtifactType.WATCH_ARTIFACT,
identifier="watch",
state=PreprodArtifactSizeMetrics.SizeAnalysisState.COMPLETED,
max_install_size=500,
max_download_size=250,
)

# Create comparison for watch metrics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
class TestToSizeInfo(TestCase):
def test_to_size_info_none_input(self):
"""Test to_size_info returns None when given None input."""
result = to_size_info(None)
result = to_size_info([])
assert result is None

def test_to_size_info_pending_state(self):
Expand All @@ -27,7 +27,7 @@ def test_to_size_info_pending_state(self):
state=PreprodArtifactSizeMetrics.SizeAnalysisState.PENDING
)

result = to_size_info(size_metrics)
result = to_size_info(list([size_metrics]))

assert isinstance(result, SizeInfoPending)
assert result.state == PreprodArtifactSizeMetrics.SizeAnalysisState.PENDING
Expand All @@ -38,7 +38,7 @@ def test_to_size_info_processing_state(self):
state=PreprodArtifactSizeMetrics.SizeAnalysisState.PROCESSING
)

result = to_size_info(size_metrics)
result = to_size_info(list([size_metrics]))

assert isinstance(result, SizeInfoProcessing)
assert result.state == PreprodArtifactSizeMetrics.SizeAnalysisState.PROCESSING
Expand All @@ -47,17 +47,54 @@ def test_to_size_info_completed_state(self):
"""Test to_size_info returns SizeInfoCompleted for COMPLETED state."""
size_metrics = PreprodArtifactSizeMetrics(
state=PreprodArtifactSizeMetrics.SizeAnalysisState.COMPLETED,
metrics_artifact_type=PreprodArtifactSizeMetrics.MetricsArtifactType.MAIN_ARTIFACT,
max_install_size=1024000,
max_download_size=512000,
)

result = to_size_info(size_metrics)
result = to_size_info(list([size_metrics]))

assert isinstance(result, SizeInfoCompleted)
assert result.state == PreprodArtifactSizeMetrics.SizeAnalysisState.COMPLETED
assert result.install_size_bytes == 1024000
assert result.download_size_bytes == 512000

def test_to_size_info_completed_state_with_multiple_metrics(self):
"""Test to_size_info returns SizeInfoCompleted for COMPLETED state with multiple metrics."""
size_metrics = [
PreprodArtifactSizeMetrics(
state=PreprodArtifactSizeMetrics.SizeAnalysisState.COMPLETED,
metrics_artifact_type=PreprodArtifactSizeMetrics.MetricsArtifactType.MAIN_ARTIFACT,
max_install_size=1024000,
max_download_size=512000,
),
PreprodArtifactSizeMetrics(
state=PreprodArtifactSizeMetrics.SizeAnalysisState.COMPLETED,
metrics_artifact_type=PreprodArtifactSizeMetrics.MetricsArtifactType.WATCH_ARTIFACT,
max_install_size=512000,
max_download_size=256000,
),
]

result = to_size_info(size_metrics)

assert isinstance(result, SizeInfoCompleted)
assert result.install_size_bytes == 1024000
assert result.download_size_bytes == 512000
assert len(result.size_metrics) == 2
assert (
result.size_metrics[0].metrics_artifact_type
== PreprodArtifactSizeMetrics.MetricsArtifactType.MAIN_ARTIFACT
)
assert result.size_metrics[0].install_size_bytes == 1024000
assert result.size_metrics[0].download_size_bytes == 512000
assert (
result.size_metrics[1].metrics_artifact_type
== PreprodArtifactSizeMetrics.MetricsArtifactType.WATCH_ARTIFACT
)
assert result.size_metrics[1].install_size_bytes == 512000
assert result.size_metrics[1].download_size_bytes == 256000

def test_to_size_info_failed_state(self):
"""Test to_size_info returns SizeInfoFailed for FAILED state."""
size_metrics = PreprodArtifactSizeMetrics(
Expand All @@ -66,7 +103,7 @@ def test_to_size_info_failed_state(self):
error_message="Analysis timed out after 30 minutes",
)

result = to_size_info(size_metrics)
result = to_size_info(list([size_metrics]))

assert isinstance(result, SizeInfoFailed)
assert result.state == PreprodArtifactSizeMetrics.SizeAnalysisState.FAILED
Expand All @@ -91,7 +128,7 @@ def test_to_size_info_failed_state_with_different_error_codes(self):
error_message=error_message,
)

result = to_size_info(size_metrics)
result = to_size_info(list([size_metrics]))

assert isinstance(result, SizeInfoFailed)
assert result.error_code == error_code
Expand All @@ -101,11 +138,12 @@ def test_to_size_info_completed_with_zero_sizes(self):
"""Test to_size_info handles completed state with zero sizes."""
size_metrics = PreprodArtifactSizeMetrics(
state=PreprodArtifactSizeMetrics.SizeAnalysisState.COMPLETED,
metrics_artifact_type=PreprodArtifactSizeMetrics.MetricsArtifactType.MAIN_ARTIFACT,
max_install_size=0,
max_download_size=0,
)

result = to_size_info(size_metrics)
result = to_size_info(list([size_metrics]))

assert isinstance(result, SizeInfoCompleted)
assert result.install_size_bytes == 0
Expand All @@ -115,11 +153,12 @@ def test_to_size_info_completed_with_large_sizes(self):
"""Test to_size_info handles completed state with large file sizes."""
size_metrics = PreprodArtifactSizeMetrics(
state=PreprodArtifactSizeMetrics.SizeAnalysisState.COMPLETED,
metrics_artifact_type=PreprodArtifactSizeMetrics.MetricsArtifactType.MAIN_ARTIFACT,
max_install_size=5000000000, # ~5GB
max_download_size=2000000000, # ~2GB
)

result = to_size_info(size_metrics)
result = to_size_info(list([size_metrics]))

assert isinstance(result, SizeInfoCompleted)
assert result.install_size_bytes == 5000000000
Expand All @@ -130,20 +169,21 @@ def test_to_size_info_invalid_state_raises_error(self):
size_metrics = PreprodArtifactSizeMetrics(state=999) # Invalid state

with pytest.raises(ValueError, match="Unknown SizeAnalysisState 999"):
to_size_info(size_metrics)
to_size_info(list([size_metrics]))

def test_to_size_info_completed_state_missing_size_fields(self):
"""Test to_size_info raises ValueError when COMPLETED state has None size fields."""
size_metrics = PreprodArtifactSizeMetrics(
state=PreprodArtifactSizeMetrics.SizeAnalysisState.COMPLETED,
metrics_artifact_type=PreprodArtifactSizeMetrics.MetricsArtifactType.MAIN_ARTIFACT,
max_install_size=None,
max_download_size=None,
)

with pytest.raises(
ValueError, match="COMPLETED state requires both max_install_size and max_download_size"
):
to_size_info(size_metrics)
to_size_info(list([size_metrics]))

def test_to_size_info_failed_state_no_error_code(self):
"""Test to_size_info raises ValueError when FAILED state has only error_code."""
Expand All @@ -156,7 +196,7 @@ def test_to_size_info_failed_state_no_error_code(self):
with pytest.raises(
ValueError, match="FAILED state requires both error_code and error_message"
):
to_size_info(size_metrics)
to_size_info(list([size_metrics]))

def test_to_size_info_failed_state_no_error_message(self):
"""Test to_size_info raises ValueError when FAILED state has only error_message."""
Expand All @@ -169,4 +209,4 @@ def test_to_size_info_failed_state_no_error_message(self):
with pytest.raises(
ValueError, match="FAILED state requires both error_code and error_message"
):
to_size_info(size_metrics)
to_size_info(list([size_metrics]))
Loading