|
19 | 19 | apply_high_level_inference, |
20 | 20 | deep_merge_list_replace, |
21 | 21 | load_default_baseline, |
| 22 | + migrate_config_dumb, |
22 | 23 | synthesize_configuration, |
23 | 24 | synthesize_to_file, |
24 | 25 | ) |
@@ -369,3 +370,102 @@ def test_synthesize_to_file_tightens_perms_on_overwrite(tmp_path: Path) -> None: |
369 | 370 | synthesize_to_file(lcs, str(out), str(tmp_path)) |
370 | 371 | assert stat.S_IMODE(os.stat(out).st_mode) == 0o600 |
371 | 372 | assert yaml.safe_load(out.read_text(encoding="utf-8")) == {"v": 3} |
| 373 | + |
| 374 | + |
| 375 | +# --------------------------------------------------------------------------- |
| 376 | +# migrate_config_dumb (LCORE-2337) |
| 377 | +# --------------------------------------------------------------------------- |
| 378 | + |
| 379 | + |
| 380 | +# A representative legacy run.yaml body with nested maps, lists, and env refs. |
| 381 | +_LEGACY_RUN_YAML: dict[str, Any] = { |
| 382 | + "version": 2, |
| 383 | + "apis": ["agents", "inference", "safety", "vector_io"], |
| 384 | + "providers": { |
| 385 | + "inference": [ |
| 386 | + { |
| 387 | + "provider_id": "openai", |
| 388 | + "provider_type": "remote::openai", |
| 389 | + "config": { |
| 390 | + "api_key": "${env.OPENAI_API_KEY}", |
| 391 | + "allowed_models": ["gpt-4o-mini"], |
| 392 | + }, |
| 393 | + }, |
| 394 | + { |
| 395 | + "provider_id": "sentence-transformers", |
| 396 | + "provider_type": "inline::sentence-transformers", |
| 397 | + }, |
| 398 | + ], |
| 399 | + }, |
| 400 | + "safety": {"default_shield_id": "llama-guard", "excluded_categories": []}, |
| 401 | +} |
| 402 | + |
| 403 | + |
| 404 | +def _write_legacy_pair(tmp_path: Path) -> tuple[str, str]: |
| 405 | + """Write a legacy run.yaml + lightspeed-stack.yaml pair, return their paths.""" |
| 406 | + run_path = tmp_path / "run.yaml" |
| 407 | + run_path.write_text(yaml.dump(_LEGACY_RUN_YAML), encoding="utf-8") |
| 408 | + lcs = { |
| 409 | + "name": "LCS", |
| 410 | + "service": {"host": "localhost", "port": 8080}, |
| 411 | + "llama_stack": { |
| 412 | + "use_as_library_client": True, |
| 413 | + "library_client_config_path": "run.yaml", |
| 414 | + }, |
| 415 | + } |
| 416 | + lcs_path = tmp_path / "lightspeed-stack.yaml" |
| 417 | + lcs_path.write_text(yaml.dump(lcs), encoding="utf-8") |
| 418 | + return str(run_path), str(lcs_path) |
| 419 | + |
| 420 | + |
| 421 | +def test_migrate_config_dumb_structure(tmp_path: Path) -> None: |
| 422 | + """Dumb migration lifts run.yaml into native_override and drops the legacy path.""" |
| 423 | + run_path, lcs_path = _write_legacy_pair(tmp_path) |
| 424 | + out_path = tmp_path / "unified.yaml" |
| 425 | + migrate_config_dumb(run_path, lcs_path, str(out_path)) |
| 426 | + |
| 427 | + migrated = yaml.safe_load(out_path.read_text(encoding="utf-8")) |
| 428 | + # unrelated top-level content preserved |
| 429 | + assert migrated["name"] == "LCS" |
| 430 | + assert migrated["service"] == {"host": "localhost", "port": 8080} |
| 431 | + # legacy path dropped; use_as_library_client preserved |
| 432 | + assert "library_client_config_path" not in migrated["llama_stack"] |
| 433 | + assert migrated["llama_stack"]["use_as_library_client"] is True |
| 434 | + # whole run.yaml lifted into native_override with an empty baseline |
| 435 | + assert migrated["llama_stack"]["config"]["baseline"] == "empty" |
| 436 | + assert migrated["llama_stack"]["config"]["native_override"] == _LEGACY_RUN_YAML |
| 437 | + |
| 438 | + |
| 439 | +def test_migrate_then_synthesize_reproduces_run_yaml(tmp_path: Path) -> None: |
| 440 | + """Round-trip: migrate -> synthesize reproduces the original run.yaml (R4).""" |
| 441 | + run_path, lcs_path = _write_legacy_pair(tmp_path) |
| 442 | + out_path = tmp_path / "unified.yaml" |
| 443 | + migrate_config_dumb(run_path, lcs_path, str(out_path)) |
| 444 | + |
| 445 | + migrated = yaml.safe_load(out_path.read_text(encoding="utf-8")) |
| 446 | + synthesized = synthesize_configuration(migrated) |
| 447 | + assert synthesized == _LEGACY_RUN_YAML |
| 448 | + |
| 449 | + |
| 450 | +def test_migrate_config_dumb_writes_output_0600(tmp_path: Path) -> None: |
| 451 | + """The migrated file may carry lifted secrets, so it is written owner-only.""" |
| 452 | + run_path, lcs_path = _write_legacy_pair(tmp_path) |
| 453 | + out_path = tmp_path / "unified.yaml" |
| 454 | + migrate_config_dumb(run_path, lcs_path, str(out_path)) |
| 455 | + assert stat.S_IMODE(os.stat(out_path).st_mode) == 0o600 |
| 456 | + |
| 457 | + |
| 458 | +def test_migrate_config_dumb_rejects_non_mapping_inputs(tmp_path: Path) -> None: |
| 459 | + """An empty/comment-only input fails with a clear ValueError, not AttributeError.""" |
| 460 | + run_path, lcs_path = _write_legacy_pair(tmp_path) |
| 461 | + out_path = str(tmp_path / "unified.yaml") |
| 462 | + |
| 463 | + empty_lcs = tmp_path / "empty-lcs.yaml" |
| 464 | + empty_lcs.write_text("# only a comment\n", encoding="utf-8") |
| 465 | + with pytest.raises(ValueError, match="did not parse to a mapping"): |
| 466 | + migrate_config_dumb(run_path, str(empty_lcs), out_path) |
| 467 | + |
| 468 | + empty_run = tmp_path / "empty-run.yaml" |
| 469 | + empty_run.write_text("", encoding="utf-8") |
| 470 | + with pytest.raises(ValueError, match="did not parse to a mapping"): |
| 471 | + migrate_config_dumb(str(empty_run), lcs_path, out_path) |
0 commit comments