|
13 | 13 | import pytest |
14 | 14 |
|
15 | 15 | from vgi.scalar_function import ScalarFunction |
16 | | -from vgi.serve import create_app, load_worker_class |
| 16 | +from vgi.serve import _resolve_describe, _resolve_signing_key, create_app, load_worker_class |
17 | 17 | from vgi.worker import Worker |
18 | 18 |
|
19 | 19 | # --------------------------------------------------------------------------- |
@@ -198,6 +198,64 @@ def test_describe_disabled(self) -> None: |
198 | 198 |
|
199 | 199 | assert isinstance(app, falcon.App) |
200 | 200 |
|
| 201 | + def test_signing_key_passed(self) -> None: |
| 202 | + """Explicit signing_key is accepted without warning.""" |
| 203 | + import warnings |
| 204 | + |
| 205 | + with warnings.catch_warnings(): |
| 206 | + warnings.simplefilter("error", UserWarning) |
| 207 | + app = create_app(_SingleWorker, signing_key=b"test-secret-key-1234") |
| 208 | + |
| 209 | + import falcon |
| 210 | + |
| 211 | + assert isinstance(app, falcon.App) |
| 212 | + |
| 213 | + |
| 214 | +# --------------------------------------------------------------------------- |
| 215 | +# Tests: env var helpers |
| 216 | +# --------------------------------------------------------------------------- |
| 217 | + |
| 218 | + |
| 219 | +class TestResolveSigningKey: |
| 220 | + """Tests for _resolve_signing_key().""" |
| 221 | + |
| 222 | + def test_unset_returns_none(self, monkeypatch: pytest.MonkeyPatch) -> None: |
| 223 | + """No env var returns None.""" |
| 224 | + monkeypatch.delenv("VGI_SIGNING_KEY", raising=False) |
| 225 | + assert _resolve_signing_key() is None |
| 226 | + |
| 227 | + def test_empty_returns_none(self, monkeypatch: pytest.MonkeyPatch) -> None: |
| 228 | + """Empty string returns None.""" |
| 229 | + monkeypatch.setenv("VGI_SIGNING_KEY", "") |
| 230 | + assert _resolve_signing_key() is None |
| 231 | + |
| 232 | + def test_value_returns_bytes(self, monkeypatch: pytest.MonkeyPatch) -> None: |
| 233 | + """Non-empty value returns UTF-8 encoded bytes.""" |
| 234 | + monkeypatch.setenv("VGI_SIGNING_KEY", "my-secret") |
| 235 | + assert _resolve_signing_key() == b"my-secret" |
| 236 | + |
| 237 | + |
| 238 | +class TestResolveDescribe: |
| 239 | + """Tests for _resolve_describe().""" |
| 240 | + |
| 241 | + def test_unset_uses_cli(self, monkeypatch: pytest.MonkeyPatch) -> None: |
| 242 | + """No env var passes through CLI value.""" |
| 243 | + monkeypatch.delenv("VGI_ENABLE_DESCRIBE", raising=False) |
| 244 | + assert _resolve_describe(True) is True |
| 245 | + assert _resolve_describe(False) is False |
| 246 | + |
| 247 | + def test_env_true_overrides(self, monkeypatch: pytest.MonkeyPatch) -> None: |
| 248 | + """Truthy env values enable describe regardless of CLI.""" |
| 249 | + for val in ("1", "true", "yes", "True", "YES"): |
| 250 | + monkeypatch.setenv("VGI_ENABLE_DESCRIBE", val) |
| 251 | + assert _resolve_describe(False) is True |
| 252 | + |
| 253 | + def test_env_false_overrides(self, monkeypatch: pytest.MonkeyPatch) -> None: |
| 254 | + """Falsy env values disable describe regardless of CLI.""" |
| 255 | + for val in ("0", "false", "no", "False", "NO"): |
| 256 | + monkeypatch.setenv("VGI_ENABLE_DESCRIBE", val) |
| 257 | + assert _resolve_describe(True) is False |
| 258 | + |
201 | 259 |
|
202 | 260 | # --------------------------------------------------------------------------- |
203 | 261 | # Tests: CLI integration |
@@ -319,3 +377,77 @@ def test_auto_discover_module(self) -> None: |
319 | 377 | finally: |
320 | 378 | proc.terminate() |
321 | 379 | proc.wait(timeout=5) |
| 380 | + |
| 381 | + def test_describe_env_var_disables_worker_page(self) -> None: |
| 382 | + """VGI_ENABLE_DESCRIBE=0 disables the worker description page.""" |
| 383 | + import urllib.error |
| 384 | + import urllib.request |
| 385 | + |
| 386 | + port = _free_port() |
| 387 | + env = os.environ.copy() |
| 388 | + env["VGI_ENABLE_DESCRIBE"] = "0" |
| 389 | + proc = subprocess.Popen( |
| 390 | + [ |
| 391 | + sys.executable, |
| 392 | + "-m", |
| 393 | + "vgi.serve", |
| 394 | + "vgi.examples.worker:ExampleWorker", |
| 395 | + "--http", |
| 396 | + "--host", |
| 397 | + "127.0.0.1", |
| 398 | + "--port", |
| 399 | + str(port), |
| 400 | + ], |
| 401 | + stdout=subprocess.PIPE, |
| 402 | + stderr=subprocess.PIPE, |
| 403 | + text=True, |
| 404 | + env=env, |
| 405 | + ) |
| 406 | + try: |
| 407 | + assert proc.stdout is not None |
| 408 | + port_line = proc.stdout.readline() |
| 409 | + assert port_line.strip() == f"PORT:{port}" |
| 410 | + |
| 411 | + time.sleep(0.5) |
| 412 | + |
| 413 | + # Worker page should not be served (404 or 405, not 200) |
| 414 | + url = f"http://127.0.0.1:{port}/vgi/worker" |
| 415 | + with pytest.raises(urllib.error.HTTPError) as exc_info: |
| 416 | + urllib.request.urlopen(url, timeout=5) |
| 417 | + assert exc_info.value.code in (404, 405) |
| 418 | + finally: |
| 419 | + proc.terminate() |
| 420 | + proc.wait(timeout=5) |
| 421 | + |
| 422 | + def test_signing_key_env_var(self) -> None: |
| 423 | + """VGI_SIGNING_KEY suppresses the random key warning.""" |
| 424 | + port = _free_port() |
| 425 | + env = os.environ.copy() |
| 426 | + env["VGI_SIGNING_KEY"] = "test-key-for-ci" |
| 427 | + proc = subprocess.Popen( |
| 428 | + [ |
| 429 | + sys.executable, |
| 430 | + "-m", |
| 431 | + "vgi.serve", |
| 432 | + "vgi.examples.worker:ExampleWorker", |
| 433 | + "--http", |
| 434 | + "--host", |
| 435 | + "127.0.0.1", |
| 436 | + "--port", |
| 437 | + str(port), |
| 438 | + ], |
| 439 | + stdout=subprocess.PIPE, |
| 440 | + stderr=subprocess.PIPE, |
| 441 | + text=True, |
| 442 | + env=env, |
| 443 | + ) |
| 444 | + try: |
| 445 | + assert proc.stdout is not None |
| 446 | + port_line = proc.stdout.readline() |
| 447 | + assert port_line.strip() == f"PORT:{port}" |
| 448 | + finally: |
| 449 | + proc.terminate() |
| 450 | + proc.wait(timeout=5) |
| 451 | + assert proc.stderr is not None |
| 452 | + stderr = proc.stderr.read() |
| 453 | + assert "No signing_key provided" not in stderr |
0 commit comments