|
15 | 15 | # limitations under the License. |
16 | 16 |
|
17 | 17 | import unittest |
| 18 | +import re |
| 19 | +import threading |
18 | 20 | from datetime import datetime, timezone |
| 21 | +from concurrent.futures import ThreadPoolExecutor |
| 22 | +from collections import Counter |
19 | 23 |
|
20 | 24 | from pypaimon.api.auth import ( |
21 | 25 | DLFAuthProvider, |
@@ -150,6 +154,103 @@ def test_parse_signing_algo_from_uri(self): |
150 | 154 | self.assertEqual("default", parse("")) |
151 | 155 | self.assertEqual("default", parse(None)) |
152 | 156 |
|
| 157 | + def test_openapi_sign_headers_with_enhanced_nonce(self): |
| 158 | + """Test enhanced nonce generation.""" |
| 159 | + signer = DLFOpenApiSigner() |
| 160 | + body = '{"CategoryName":"test","CategoryType":"UNSTRUCTURED"}' |
| 161 | + now = datetime(2025, 4, 16, 3, 44, 46, tzinfo=timezone.utc) |
| 162 | + host = "dlfnext.cn-beijing.aliyuncs.com" |
| 163 | + |
| 164 | + headers = signer.sign_headers(body, now, None, host) |
| 165 | + |
| 166 | + self.assertIsNotNone(headers.get("Date")) |
| 167 | + self.assertEqual("application/json", headers.get("Accept")) |
| 168 | + self.assertIsNotNone(headers.get("Content-MD5")) |
| 169 | + self.assertEqual("application/json", headers.get("Content-Type")) |
| 170 | + self.assertEqual(host, headers.get("Host")) |
| 171 | + self.assertEqual("HMAC-SHA1", headers.get("x-acs-signature-method")) |
| 172 | + |
| 173 | + nonce_value = headers.get("x-acs-signature-nonce") |
| 174 | + self.assertIsNotNone(nonce_value) |
| 175 | + |
| 176 | + uuid_pattern = re.compile(r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') |
| 177 | + uuid_match = uuid_pattern.search(nonce_value) |
| 178 | + self.assertIsNotNone(uuid_match, f"No UUID pattern found in nonce: {nonce_value}") |
| 179 | + |
| 180 | + digit_pattern = re.compile(r'\d+') |
| 181 | + digit_matches = digit_pattern.findall(nonce_value) |
| 182 | + self.assertGreater(len(digit_matches), 0, f"No numeric parts found in nonce: {nonce_value}") |
| 183 | + |
| 184 | + timestamp_found = any(len(part) >= 10 for part in digit_matches) |
| 185 | + self.assertTrue(timestamp_found, f"No timestamp-like part found in nonce: {nonce_value}") |
| 186 | + |
| 187 | + self.assertEqual("1.0", headers.get("x-acs-signature-version")) |
| 188 | + self.assertEqual("2026-01-18", headers.get("x-acs-version")) |
| 189 | + |
| 190 | + def test_concurrent_nonce_generation(self): |
| 191 | + """Test nonce generation thread safety.""" |
| 192 | + signer = DLFOpenApiSigner() |
| 193 | + body = '{"test":"data"}' |
| 194 | + now = datetime.now(timezone.utc) |
| 195 | + host = "test-host" |
| 196 | + thread_count = 10 |
| 197 | + iterations_per_thread = 50 |
| 198 | + |
| 199 | + nonces = set() |
| 200 | + |
| 201 | + def worker(): |
| 202 | + for _ in range(iterations_per_thread): |
| 203 | + headers = signer.sign_headers(body, now, None, host) |
| 204 | + nonce = headers.get("x-acs-signature-nonce") |
| 205 | + nonces.add(nonce) |
| 206 | + |
| 207 | + threads = [] |
| 208 | + for _ in range(thread_count): |
| 209 | + thread = threading.Thread(target=worker) |
| 210 | + threads.append(thread) |
| 211 | + thread.start() |
| 212 | + |
| 213 | + for thread in threads: |
| 214 | + thread.join() |
| 215 | + |
| 216 | + expected_total = thread_count * iterations_per_thread |
| 217 | + self.assertEqual(expected_total, len(nonces), |
| 218 | + f"Expected {expected_total} unique nonces, but got {len(nonces)}. " |
| 219 | + f"Possible duplicate nonces generated.") |
| 220 | + |
| 221 | + def test_parameter_validation(self): |
| 222 | + """Test parameter validation.""" |
| 223 | + signer = DLFOpenApiSigner() |
| 224 | + |
| 225 | + with self.assertRaises(ValueError) as context: |
| 226 | + signer.sign_headers("body", None, "token", "host") |
| 227 | + self.assertIn("'now' cannot be None", str(context.exception)) |
| 228 | + |
| 229 | + now = datetime.now(timezone.utc) |
| 230 | + with self.assertRaises(ValueError) as context: |
| 231 | + signer.sign_headers("body", now, "token", None) |
| 232 | + self.assertIn("'host' cannot be None", str(context.exception)) |
| 233 | + |
| 234 | + token = DLFToken("ak", "sk", "token", None) |
| 235 | + rest_param = RESTAuthParameter("GET", "/", "", {}) |
| 236 | + headers = signer.sign_headers("", now, "", "host") |
| 237 | + |
| 238 | + with self.assertRaises(ValueError) as context: |
| 239 | + signer.authorization(None, token, "host", headers) |
| 240 | + self.assertIn("'rest_auth_parameter' cannot be None", str(context.exception)) |
| 241 | + |
| 242 | + with self.assertRaises(ValueError) as context: |
| 243 | + signer.authorization(rest_param, None, "host", headers) |
| 244 | + self.assertIn("'token' cannot be None", str(context.exception)) |
| 245 | + |
| 246 | + with self.assertRaises(ValueError) as context: |
| 247 | + signer.authorization(rest_param, token, None, headers) |
| 248 | + self.assertIn("'host' cannot be None", str(context.exception)) |
| 249 | + |
| 250 | + with self.assertRaises(ValueError) as context: |
| 251 | + signer.authorization(rest_param, token, "host", None) |
| 252 | + self.assertIn("'sign_headers' cannot be None", str(context.exception)) |
| 253 | + |
153 | 254 |
|
154 | 255 | if __name__ == '__main__': |
155 | 256 | unittest.main() |
0 commit comments