Skip to content

Commit 70ab10d

Browse files
authored
Merge pull request #46 from schwehr/test_batch_mover
Add test_batch_mover.py
2 parents f3d59ec + 508f71c commit 70ab10d

1 file changed

Lines changed: 190 additions & 0 deletions

File tree

tests/test_batch_mover.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
"""Tests for batch_mover module.
2+
3+
SPDX-License-Identifier: Apache-2.0
4+
"""
5+
6+
import io
7+
import signal
8+
import sys
9+
import time
10+
from unittest import mock
11+
12+
import pytest
13+
14+
import ee
15+
from geeadd import batch_mover
16+
17+
18+
def test_retry_success_first_try():
19+
"""Test that the decorator doesn't retry if the function succeeds."""
20+
mock_func = mock.Mock(return_value="success")
21+
22+
@batch_mover.retry_on_ee_error(max_retries=3, backoff_factor=0.01)
23+
def func_to_decorate():
24+
return mock_func()
25+
26+
result = func_to_decorate()
27+
28+
assert result == "success"
29+
mock_func.assert_called_once()
30+
31+
32+
@mock.patch.object(time, "sleep")
33+
def test_retry_rate_limit_success_after_retry(mock_sleep):
34+
"""Test retry on rate limit EEException, succeeding on retry."""
35+
mock_func = mock.Mock()
36+
mock_func.side_effect = [
37+
ee.EEException("Rate limit exceeded"),
38+
"success",
39+
]
40+
41+
@batch_mover.retry_on_ee_error(max_retries=3, backoff_factor=0.01)
42+
def func_to_decorate():
43+
return mock_func()
44+
45+
result = func_to_decorate()
46+
47+
assert result == "success"
48+
assert mock_func.call_count == 2
49+
mock_sleep.assert_called_once()
50+
51+
52+
@mock.patch.object(time, "sleep")
53+
def test_retry_quota_error_success_after_retry(mock_sleep):
54+
"""Test retry on quota EEException, succeeding on retry."""
55+
mock_func = mock.Mock()
56+
mock_func.side_effect = [
57+
ee.EEException("Quota exceeded for project"),
58+
"success",
59+
]
60+
61+
@batch_mover.retry_on_ee_error(max_retries=3, backoff_factor=0.01)
62+
def func_to_decorate():
63+
return mock_func()
64+
65+
result = func_to_decorate()
66+
67+
assert result == "success"
68+
assert mock_func.call_count == 2
69+
mock_sleep.assert_called_once()
70+
71+
72+
@mock.patch.object(time, "sleep")
73+
def test_retry_failure_after_max_retries(mock_sleep):
74+
"""Test that EEException is raised after max_retries."""
75+
mock_func = mock.Mock(side_effect=ee.EEException("Rate limit exceeded"))
76+
77+
@batch_mover.retry_on_ee_error(max_retries=3, backoff_factor=0.01)
78+
def func_to_decorate():
79+
return mock_func()
80+
81+
with pytest.raises(ee.EEException, match="Rate limit exceeded"):
82+
func_to_decorate()
83+
84+
assert mock_func.call_count == 3
85+
assert mock_sleep.call_count == 2
86+
87+
88+
@mock.patch.object(time, "sleep")
89+
def test_retry_non_rate_limit_ee_exception(mock_sleep):
90+
"""Test no retry on non-rate-limit EEException."""
91+
mock_func = mock.Mock(side_effect=ee.EEException("Asset not found"))
92+
93+
@batch_mover.retry_on_ee_error(max_retries=3, backoff_factor=0.01)
94+
def func_to_decorate():
95+
return mock_func()
96+
97+
with pytest.raises(ee.EEException, match="Asset not found"):
98+
func_to_decorate()
99+
100+
mock_func.assert_called_once()
101+
mock_sleep.assert_not_called()
102+
103+
104+
@mock.patch.object(time, "sleep")
105+
def test_retry_other_exception(mock_sleep):
106+
"""Test no retry on non-EEException."""
107+
mock_func = mock.Mock(side_effect=ValueError("Some other error"))
108+
109+
@batch_mover.retry_on_ee_error(max_retries=3, backoff_factor=0.01)
110+
def func_to_decorate():
111+
return mock_func()
112+
113+
with pytest.raises(ValueError, match="Some other error"):
114+
func_to_decorate()
115+
116+
mock_func.assert_called_once()
117+
mock_sleep.assert_not_called()
118+
119+
def test_handle_interrupt_first_call():
120+
"""Test handle_interrupt on first call."""
121+
batch_mover.interrupt_received = False
122+
batch_mover.handle_interrupt(signal.SIGINT, None)
123+
assert batch_mover.interrupt_received
124+
125+
126+
def test_handle_interrupt_second_call():
127+
"""Test handle_interrupt on second call."""
128+
batch_mover.interrupt_received = True
129+
with mock.patch.object(sys, "exit") as mock_exit:
130+
batch_mover.handle_interrupt(signal.SIGINT, None)
131+
mock_exit.assert_called_once_with(1)
132+
133+
134+
@pytest.mark.parametrize(
135+
"input_str, expected_output",
136+
[
137+
("image", "Image"),
138+
("image collection", "Image Collection"),
139+
("feature view", "Feature View"),
140+
("table", "Table"),
141+
("", ""),
142+
("ALREADY TITLE CASE", "Already Title Case"),
143+
("asset", "Asset"),
144+
],
145+
)
146+
def test_camel_case(input_str, expected_output):
147+
assert batch_mover.camel_case(input_str) == expected_output
148+
149+
150+
@mock.patch.object(ee.data, "getAsset")
151+
def test_get_asset_safe_success(mock_get_asset):
152+
"""Test get_asset_safe successfully returns asset."""
153+
mock_get_asset.return_value = {"id": "test/asset", "type": "IMAGE"}
154+
asset = batch_mover.get_asset_safe("test/asset")
155+
assert asset == {"id": "test/asset", "type": "IMAGE"}
156+
mock_get_asset.assert_called_once_with("test/asset")
157+
158+
@mock.patch.object(ee.data, "getAsset")
159+
def test_get_asset_safe_not_found(mock_get_asset):
160+
"""Test get_asset_safe returns None when asset not found."""
161+
mock_get_asset.side_effect = ee.EEException("Asset test/asset not found.")
162+
asset = batch_mover.get_asset_safe("test/asset")
163+
assert asset is None
164+
mock_get_asset.assert_called_once_with("test/asset")
165+
166+
@mock.patch.object(ee.data, "getAsset")
167+
def test_get_asset_safe_does_not_exist(mock_get_asset):
168+
"""Test get_asset_safe returns None when asset does not exist."""
169+
mock_get_asset.side_effect = ee.EEException(
170+
"Asset projects/proj/assets/asset does not exist"
171+
)
172+
asset = batch_mover.get_asset_safe("projects/proj/assets/asset")
173+
assert asset is None
174+
mock_get_asset.assert_called_once_with("projects/proj/assets/asset")
175+
176+
@mock.patch.object(ee.data, "getAsset")
177+
def test_get_asset_safe_ee_exception_propagates(mock_get_asset):
178+
"""Test get_asset_safe raises other EEExceptions."""
179+
mock_get_asset.side_effect = ee.EEException("Some other EE error")
180+
with pytest.raises(ee.EEException, match="Some other EE error"):
181+
batch_mover.get_asset_safe("test/asset")
182+
mock_get_asset.assert_called_once_with("test/asset")
183+
184+
@mock.patch.object(ee.data, "getAsset")
185+
def test_get_asset_safe_other_exception(mock_get_asset):
186+
"""Test get_asset_safe returns None on other exceptions."""
187+
mock_get_asset.side_effect = ValueError("Some other error")
188+
asset = batch_mover.get_asset_safe("test/asset")
189+
assert asset is None
190+
mock_get_asset.assert_called_once_with("test/asset")

0 commit comments

Comments
 (0)