test_config_manager.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. """Tests for config migration behavior."""
  2. from __future__ import annotations
  3. from pathlib import Path
  4. from unittest.mock import patch
  5. import yaml
  6. from cli.core.config.config_manager import (
  7. DEFAULT_LIBRARY_BRANCH,
  8. DEFAULT_LIBRARY_DIRECTORY,
  9. DEFAULT_LIBRARY_NAME,
  10. DEFAULT_LIBRARY_URL,
  11. LEGACY_DEFAULT_LIBRARY_URL,
  12. ConfigManager,
  13. is_legacy_default_library_url,
  14. )
  15. from cli.core.exceptions import ConfigError
  16. def _write_config(path: Path, data: dict) -> None:
  17. path.parent.mkdir(parents=True, exist_ok=True)
  18. path.write_text(yaml.safe_dump(data), encoding="utf-8")
  19. def test_default_config_uses_new_library_repo(tmp_path: Path) -> None:
  20. """A fresh config should point at the new default template library."""
  21. config_path = tmp_path / "config.yaml"
  22. manager = ConfigManager(config_path=config_path)
  23. libraries = manager.get_libraries()
  24. assert len(libraries) == 1
  25. assert libraries[0]["name"] == DEFAULT_LIBRARY_NAME
  26. assert libraries[0]["url"] == DEFAULT_LIBRARY_URL
  27. assert libraries[0]["branch"] == DEFAULT_LIBRARY_BRANCH
  28. assert libraries[0]["directory"] == DEFAULT_LIBRARY_DIRECTORY
  29. def test_migrate_legacy_default_library_and_queue_notice(tmp_path: Path) -> None:
  30. """Legacy christianlempa/boilerplates entries should be rewritten."""
  31. config_path = tmp_path / "config.yaml"
  32. ConfigManager.consume_migration_notices()
  33. _write_config(
  34. config_path,
  35. {
  36. "defaults": {},
  37. "preferences": {},
  38. "libraries": [
  39. {
  40. "name": DEFAULT_LIBRARY_NAME,
  41. "type": "git",
  42. "url": LEGACY_DEFAULT_LIBRARY_URL,
  43. "branch": "feature/test",
  44. "directory": "library",
  45. "enabled": True,
  46. },
  47. {
  48. "name": "custom",
  49. "type": "git",
  50. "url": "https://github.com/example/custom-library.git",
  51. "branch": "dev",
  52. "directory": "templates",
  53. "enabled": True,
  54. },
  55. ],
  56. },
  57. )
  58. manager = ConfigManager(config_path=config_path)
  59. libraries = manager.get_libraries()
  60. notices = ConfigManager.consume_migration_notices()
  61. assert libraries[0]["name"] == DEFAULT_LIBRARY_NAME
  62. assert libraries[0]["url"] == DEFAULT_LIBRARY_URL
  63. assert libraries[0]["branch"] == "feature/test"
  64. assert libraries[0]["directory"] == DEFAULT_LIBRARY_DIRECTORY
  65. assert libraries[0]["enabled"] is True
  66. assert libraries[1]["name"] == "custom"
  67. assert libraries[1]["url"] == "https://github.com/example/custom-library.git"
  68. assert libraries[1]["branch"] == "dev"
  69. assert libraries[1]["directory"] == "templates"
  70. assert len(notices) == 1
  71. assert "boilerplates-library" in notices[0].message
  72. assert "default" in notices[0].message
  73. # Notices are consumed once.
  74. assert ConfigManager.consume_migration_notices() == []
  75. def test_does_not_migrate_custom_non_boilerplates_library(tmp_path: Path) -> None:
  76. """Non-matching custom library entries should be left untouched."""
  77. config_path = tmp_path / "config.yaml"
  78. ConfigManager.consume_migration_notices()
  79. _write_config(
  80. config_path,
  81. {
  82. "defaults": {},
  83. "preferences": {},
  84. "libraries": [
  85. {
  86. "name": DEFAULT_LIBRARY_NAME,
  87. "type": "git",
  88. "url": "/Users/test/local/boilerplates",
  89. "branch": "feature/test",
  90. "directory": ".",
  91. "enabled": True,
  92. }
  93. ],
  94. },
  95. )
  96. manager = ConfigManager(config_path=config_path)
  97. libraries = manager.get_libraries()
  98. assert libraries[0]["name"] == DEFAULT_LIBRARY_NAME
  99. assert libraries[0]["url"] == "/Users/test/local/boilerplates"
  100. assert libraries[0]["branch"] == "feature/test"
  101. assert libraries[0]["directory"] == "."
  102. assert ConfigManager.consume_migration_notices() == []
  103. def test_migrates_any_library_pointing_to_legacy_boilerplates_repo(tmp_path: Path) -> None:
  104. """Any library URL pointing to christianlempa/boilerplates should migrate."""
  105. config_path = tmp_path / "config.yaml"
  106. ConfigManager.consume_migration_notices()
  107. _write_config(
  108. config_path,
  109. {
  110. "defaults": {},
  111. "preferences": {},
  112. "libraries": [
  113. {
  114. "name": "custom",
  115. "type": "git",
  116. "url": "git@github.com:ChristianLempa/boilerplates.git",
  117. "branch": "feature/test",
  118. "directory": "templates",
  119. "enabled": True,
  120. }
  121. ],
  122. },
  123. )
  124. manager = ConfigManager(config_path=config_path)
  125. libraries = manager.get_libraries()
  126. notices = ConfigManager.consume_migration_notices()
  127. assert libraries[0]["name"] == "custom"
  128. assert libraries[0]["url"] == DEFAULT_LIBRARY_URL
  129. assert libraries[0]["branch"] == "feature/test"
  130. assert libraries[0]["directory"] == DEFAULT_LIBRARY_DIRECTORY
  131. assert len(notices) == 1
  132. assert "custom" in notices[0].message
  133. def test_legacy_library_url_matcher_handles_common_git_url_variants() -> None:
  134. """Legacy repo detection should match HTTPS and SSH GitHub URL forms."""
  135. assert is_legacy_default_library_url("https://github.com/christianlempa/boilerplates.git")
  136. assert is_legacy_default_library_url("https://github.com/ChristianLempa/boilerplates")
  137. assert is_legacy_default_library_url("git@github.com:ChristianLempa/boilerplates.git")
  138. assert not is_legacy_default_library_url("https://github.com/christianlempa/boilerplates-library.git")
  139. def test_migration_write_failure_raises_config_error(tmp_path: Path) -> None:
  140. """Migration failures should surface as config errors instead of being swallowed."""
  141. config_path = tmp_path / "config.yaml"
  142. _write_config(
  143. config_path,
  144. {
  145. "defaults": {},
  146. "preferences": {},
  147. "libraries": [
  148. {
  149. "name": DEFAULT_LIBRARY_NAME,
  150. "type": "git",
  151. "url": LEGACY_DEFAULT_LIBRARY_URL,
  152. "branch": "main",
  153. "directory": "library",
  154. "enabled": True,
  155. }
  156. ],
  157. },
  158. )
  159. with patch.object(ConfigManager, "_write_config", side_effect=ConfigError("disk full")):
  160. try:
  161. ConfigManager(config_path=config_path)
  162. except ConfigError as exc:
  163. assert "disk full" in str(exc)
  164. else:
  165. raise AssertionError("Expected ConfigError for failed config migration write")