test_installer.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import importlib.util
  2. import tempfile
  3. import unittest
  4. from pathlib import Path
  5. from unittest.mock import patch
  6. import yaml
  7. from test_profile_runtime_repair import SERVER, METHODS
  8. spec = importlib.util.spec_from_file_location('bridge_installer', Path(__file__).parents[3] / 'scripts/install-hermes-bridge.py')
  9. installer = importlib.util.module_from_spec(spec)
  10. spec.loader.exec_module(installer)
  11. class InstallerTests(unittest.TestCase):
  12. def setUp(self):
  13. self.temp = tempfile.TemporaryDirectory()
  14. self.root = Path(self.temp.name)
  15. self.source = self.root / 'source'; self.source.mkdir()
  16. (self.source / 'plugin.yaml').write_text('name: yaoyao-bot-bridge\nversion: 1.2.0\n')
  17. self.home = self.root / 'hermes'; self.home.mkdir()
  18. self.config = self.home / 'config.yaml'
  19. self.config.write_text('model:\n default: keep-model\nplugins:\n enabled: [existing]\n')
  20. self.target = self.home / 'plugins' / 'yaoyao-bot-bridge'
  21. self.target.mkdir(parents=True)
  22. (self.target / 'old').write_text('previous-plugin')
  23. def tearDown(self):
  24. self.temp.cleanup()
  25. def test_upgrade_backs_up_plugin_and_preserves_unrelated_config(self):
  26. original = self.config.read_bytes()
  27. result = installer.install(self.home, 'default', self.source)
  28. self.assertEqual((Path(result['backup']) / 'config.yaml').read_bytes(), original)
  29. self.assertEqual((Path(result['backup']) / 'yaoyao-bot-bridge' / 'old').read_text(), 'previous-plugin')
  30. config = yaml.safe_load(self.config.read_text())
  31. self.assertEqual(config['model']['default'], 'keep-model')
  32. self.assertEqual(config['plugins']['enabled'], ['existing', 'yaoyao-bot-bridge'])
  33. def test_preserves_an_explicitly_disabled_plugin(self):
  34. self.config.write_text('plugins:\n disabled: [yaoyao-bot-bridge]\n')
  35. result = installer.install(self.home, 'default', self.source)
  36. self.assertTrue(result['disabled'])
  37. self.assertNotIn('yaoyao-bot-bridge', yaml.safe_load(self.config.read_text())['plugins'].get('enabled', []))
  38. def test_failed_copy_or_config_write_preserves_original_installation(self):
  39. for function in ['shutil.copytree', 'yaml.safe_dump']:
  40. with self.subTest(function=function):
  41. original = self.config.read_bytes()
  42. with patch.object(installer.shutil if function.startswith('shutil') else installer.yaml,
  43. function.split('.')[1], side_effect=RuntimeError('fixture-failure')):
  44. with self.assertRaises(RuntimeError): installer.install(self.home, 'default', self.source)
  45. self.assertEqual(self.config.read_bytes(), original)
  46. self.assertEqual((self.target / 'old').read_text(), 'previous-plugin')
  47. def test_check_is_read_only_and_enable_changes_only_the_selected_profile_plugin(self):
  48. named = self.home / 'profiles' / 'writer'; named.mkdir(parents=True)
  49. config = named / 'config.yaml'
  50. config.write_text('plugins:\n disabled: [yaoyao-bot-bridge, other]\nagent:\n disabled_toolsets: [yaoyao_bot_bridge, terminal]\nplatform_toolsets:\n cli: [web]\n')
  51. before = self.config.read_bytes()
  52. self.assertFalse(installer.check(self.home, 'writer', self.source)['enabled'])
  53. self.assertFalse((named / 'backups').exists())
  54. installer.install(self.home, 'writer', self.source, enable=True)
  55. checked = installer.check(self.home, 'writer', self.source)
  56. self.assertTrue(checked['filesCurrent']); self.assertTrue(checked['enabled']); self.assertFalse(checked['disabled'])
  57. saved = yaml.safe_load(config.read_text())
  58. self.assertEqual(saved['plugins']['disabled'], ['other'])
  59. self.assertEqual(saved['agent']['disabled_toolsets'], ['terminal'])
  60. self.assertEqual(saved['platform_toolsets']['cli'], ['web', 'yaoyao_bot_bridge'])
  61. self.assertEqual(self.config.read_bytes(), before)
  62. (named / 'plugins' / 'yaoyao-bot-bridge' / 'plugin.yaml').write_text('invalid: [')
  63. broken = installer.check(self.home, 'writer', self.source)
  64. self.assertTrue(broken['valid']); self.assertFalse(broken['filesCurrent'])
  65. def test_check_detects_same_version_code_changes(self):
  66. installer.install(self.home, 'default', self.source)
  67. self.assertTrue(installer.check(self.home, 'default', self.source)['filesCurrent'])
  68. (self.target / 'bridge_runtime.py').write_text('old implementation')
  69. self.assertFalse(installer.check(self.home, 'default', self.source)['filesCurrent'])
  70. def test_core_repair_is_backed_up_and_rolled_back_with_a_failed_plugin_install(self):
  71. installer.shutil.copyfile(Path(__file__).parents[1] / 'profile_runtime_repair.py', self.source / 'profile_runtime_repair.py')
  72. runtime = self.root / 'runtime'
  73. (runtime / 'tui_gateway').mkdir(parents=True)
  74. server = runtime / 'tui_gateway' / 'server.py'
  75. methods = runtime / 'tui_gateway' / 'methods_session.py'
  76. server.write_text(SERVER); methods.write_text(METHODS)
  77. config = self.config.read_bytes()
  78. with patch.object(installer.yaml, 'safe_dump', side_effect=RuntimeError('fixture-failure')):
  79. with self.assertRaises(RuntimeError):
  80. installer.install(self.home, 'default', self.source, repair_source=runtime)
  81. self.assertEqual(server.read_text(), SERVER)
  82. self.assertEqual(methods.read_text(), METHODS)
  83. self.assertEqual(self.config.read_bytes(), config)
  84. self.assertEqual((self.target / 'old').read_text(), 'previous-plugin')
  85. result = installer.install(self.home, 'default', self.source, repair_source=runtime)
  86. self.assertTrue(result['profileRuntimeRepaired'])
  87. self.assertIn('@_profile_scoped', methods.read_text())
  88. self.assertEqual((Path(result['backup']) / 'hermes-runtime' / 'server.py').read_text(), SERVER)
  89. self.assertEqual(yaml.safe_load(self.config.read_text())['model']['default'], 'keep-model')
  90. if __name__ == '__main__':
  91. unittest.main()