gowalk-cicd 1.0.75 → 1.0.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/action/.daemux-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.76
|
|
@@ -155,21 +155,37 @@ def _request_timeouts() -> tuple[float, float]:
|
|
|
155
155
|
)
|
|
156
156
|
|
|
157
157
|
|
|
158
|
+
def _network_error_category(exc: BaseException) -> str:
|
|
159
|
+
"""Closed diagnostic labels only; exception text can contain private proxy URLs."""
|
|
160
|
+
for error_type, category in (
|
|
161
|
+
(requests.exceptions.ProxyError, "proxy"),
|
|
162
|
+
(requests.exceptions.SSLError, "tls"),
|
|
163
|
+
(requests.exceptions.ConnectTimeout, "connect_timeout"),
|
|
164
|
+
(requests.exceptions.ReadTimeout, "read_timeout"),
|
|
165
|
+
(requests.exceptions.Timeout, "timeout"),
|
|
166
|
+
(requests.exceptions.ConnectionError, "connection"),
|
|
167
|
+
):
|
|
168
|
+
if isinstance(exc, error_type):
|
|
169
|
+
return category
|
|
170
|
+
return "request"
|
|
171
|
+
|
|
172
|
+
|
|
158
173
|
def _retry_network_error(
|
|
159
174
|
method: str, path: str, attempt: int, total: int,
|
|
160
175
|
backoffs: tuple[int, ...], exc: BaseException,
|
|
161
176
|
) -> None:
|
|
162
177
|
"""Network error path: sleep and return when retries remain, else
|
|
163
178
|
raise SystemExit (terminal). Caller ``continue``s after return."""
|
|
179
|
+
category = _network_error_category(exc)
|
|
164
180
|
if attempt >= total - 1:
|
|
165
181
|
raise SystemExit(
|
|
166
182
|
f"ASC {method} {path} network error after {total} "
|
|
167
|
-
"attempts through the configured proxy"
|
|
183
|
+
f"attempts through the configured proxy (category={category})"
|
|
168
184
|
)
|
|
169
185
|
delay = backoffs[attempt]
|
|
170
186
|
print(
|
|
171
187
|
f"ASC {method} {path} network error through the configured proxy; "
|
|
172
|
-
f"retrying in {delay}s ({attempt + 1}/{total - 1})",
|
|
188
|
+
f"retrying in {delay}s ({attempt + 1}/{total - 1}; category={category})",
|
|
173
189
|
file=sys.stderr,
|
|
174
190
|
)
|
|
175
191
|
time.sleep(delay)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Real ASC helper and Requests preparation; only socket dispatch and sleeps are replaced."""
|
|
2
|
+
import contextlib
|
|
3
|
+
import io
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import sys
|
|
7
|
+
import unittest
|
|
8
|
+
from unittest import mock
|
|
9
|
+
|
|
10
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
11
|
+
import asc_common
|
|
12
|
+
|
|
13
|
+
PRIVATE = "SYNTHETIC_PRIVATE_MARKER"
|
|
14
|
+
PROXY = f"http://account:{PRIVATE}@assigned.proxy.test:1234"
|
|
15
|
+
ENV = {"APPLE_STORE_PROXY_URL": PROXY, "NO_PROXY": "*", "no_proxy": "*",
|
|
16
|
+
"HTTPS_PROXY": "http://unrelated.proxy.test:9876", "REQUESTS_CA_BUNDLE": "",
|
|
17
|
+
"CURL_CA_BUNDLE": "", "ASC_REQUEST_TIMEOUT_CONNECT_SEC": "", "ASC_REQUEST_TIMEOUT_READ_SEC": ""}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PrivateFailure(asc_common.requests.RequestException):
|
|
21
|
+
def __str__(self):
|
|
22
|
+
raise AssertionError("Exception text must never be inspected")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@mock.patch.dict(os.environ, ENV)
|
|
26
|
+
class NetworkDiagnosticTests(unittest.TestCase):
|
|
27
|
+
def test_terminal_categories_preserve_transport_and_privacy(self):
|
|
28
|
+
errors = asc_common.requests.exceptions
|
|
29
|
+
cases = [(errors.ProxyError, "proxy"), (errors.SSLError, "tls"),
|
|
30
|
+
(errors.ConnectTimeout, "connect_timeout"), (errors.ReadTimeout, "read_timeout"),
|
|
31
|
+
(errors.Timeout, "timeout"), (errors.ConnectionError, "connection"),
|
|
32
|
+
(errors.RequestException, "request"), (PrivateFailure, "request")]
|
|
33
|
+
for error_type, category in cases:
|
|
34
|
+
with self.subTest(category=category, error_type=error_type.__name__):
|
|
35
|
+
stderr, stdout = io.StringIO(), io.StringIO()
|
|
36
|
+
with mock.patch.object(asc_common.requests.Session, "send", autospec=True,
|
|
37
|
+
side_effect=error_type(f"{PRIVATE} {PROXY}")) as sender, \
|
|
38
|
+
mock.patch.object(asc_common.time, "sleep") as sleep, \
|
|
39
|
+
contextlib.redirect_stderr(stderr), contextlib.redirect_stdout(stdout):
|
|
40
|
+
with self.assertRaises(SystemExit) as caught:
|
|
41
|
+
asc_common.request("GET", "/builds", PRIVATE, params={"limit": "200"})
|
|
42
|
+
self.assertEqual(sender.call_count, 3)
|
|
43
|
+
self.assertEqual(sleep.call_args_list, [mock.call(2), mock.call(8)])
|
|
44
|
+
self.assertEqual(stderr.getvalue().count(f"category={category}"), 2)
|
|
45
|
+
self.assertIn(f"after 3 attempts through the configured proxy (category={category})",
|
|
46
|
+
str(caught.exception))
|
|
47
|
+
self.assertNotIn(PRIVATE, stdout.getvalue() + stderr.getvalue() + str(caught.exception))
|
|
48
|
+
self.assertEqual(stdout.getvalue(), "")
|
|
49
|
+
for call in sender.call_args_list:
|
|
50
|
+
self.assertEqual(call.args[1].url, asc_common.ASC_BASE + "/builds?limit=200")
|
|
51
|
+
self.assertEqual(call.args[1].headers["Authorization"], f"Bearer {PRIVATE}")
|
|
52
|
+
self.assertEqual(call.kwargs["proxies"]["https"], PROXY)
|
|
53
|
+
self.assertEqual(call.kwargs["proxies"]["http"], PROXY)
|
|
54
|
+
self.assertEqual(call.kwargs["timeout"], (10.0, 30.0))
|
|
55
|
+
self.assertIs(call.kwargs["verify"], True)
|
|
56
|
+
|
|
57
|
+
def test_transient_failure_returns_success_without_extra_sender_calls(self):
|
|
58
|
+
response = asc_common.requests.Response()
|
|
59
|
+
response.status_code = 200
|
|
60
|
+
stderr = io.StringIO()
|
|
61
|
+
with mock.patch.object(asc_common.requests.Session, "send", autospec=True,
|
|
62
|
+
side_effect=[asc_common.requests.exceptions.ProxyError(PRIVATE), response]) as sender, \
|
|
63
|
+
mock.patch.object(asc_common.time, "sleep") as sleep, contextlib.redirect_stderr(stderr):
|
|
64
|
+
self.assertIs(asc_common.request("GET", "/builds", PRIVATE), response)
|
|
65
|
+
self.assertEqual(sender.call_count, 2)
|
|
66
|
+
sleep.assert_called_once_with(2)
|
|
67
|
+
self.assertIn("category=proxy", stderr.getvalue())
|
|
68
|
+
self.assertNotIn(PRIVATE, stderr.getvalue())
|
|
69
|
+
|
|
70
|
+
def test_single_attempt_does_not_sleep(self):
|
|
71
|
+
with mock.patch.object(asc_common.requests.Session, "send", autospec=True,
|
|
72
|
+
side_effect=asc_common.requests.exceptions.ReadTimeout(PRIVATE)) as sender, \
|
|
73
|
+
mock.patch.object(asc_common.time, "sleep") as sleep:
|
|
74
|
+
with self.assertRaisesRegex(SystemExit, "after 1 attempts.*category=read_timeout"):
|
|
75
|
+
asc_common.request("GET", "/builds", PRIVATE, max_attempts=1)
|
|
76
|
+
self.assertEqual(sender.call_count, 1)
|
|
77
|
+
sleep.assert_not_called()
|
|
78
|
+
|
|
79
|
+
def test_missing_or_invalid_proxy_refuses_before_sender(self):
|
|
80
|
+
for proxy in ["", "not-a-proxy"]:
|
|
81
|
+
with self.subTest(proxy=proxy), mock.patch.dict(os.environ, {"APPLE_STORE_PROXY_URL": proxy}), \
|
|
82
|
+
mock.patch.object(asc_common.requests.Session, "send") as sender, \
|
|
83
|
+
mock.patch.object(asc_common.time, "sleep") as sleep:
|
|
84
|
+
with self.assertRaisesRegex(SystemExit, "direct requests are refused"):
|
|
85
|
+
asc_common.request("GET", "/builds", PRIVATE)
|
|
86
|
+
sender.assert_not_called()
|
|
87
|
+
sleep.assert_not_called()
|
|
88
|
+
|
|
89
|
+
def test_http_refusal_is_not_reclassified_as_network_failure(self):
|
|
90
|
+
response = asc_common.requests.Response()
|
|
91
|
+
response.status_code = 401
|
|
92
|
+
response._content = b"unauthorized"
|
|
93
|
+
with mock.patch.object(asc_common.requests.Session, "send", return_value=response) as sender, \
|
|
94
|
+
mock.patch.object(asc_common.time, "sleep") as sleep:
|
|
95
|
+
with self.assertRaises(SystemExit) as caught:
|
|
96
|
+
asc_common.request("GET", "/builds", PRIVATE)
|
|
97
|
+
self.assertEqual(sender.call_count, 1)
|
|
98
|
+
sleep.assert_not_called()
|
|
99
|
+
self.assertEqual(str(caught.exception), "ASC GET /builds failed: 401\nunauthorized")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
unittest.main()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.76
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.76
|