SelfUpdateTest.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. <?php
  2. /*
  3. * This file is part of Psy Shell.
  4. *
  5. * (c) 2012-2023 Justin Hileman
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Psy\Test\VersionUpdater;
  11. use Psy\Exception\ErrorException;
  12. use Psy\Shell;
  13. use Psy\VersionUpdater\Checker;
  14. use Psy\VersionUpdater\Downloader;
  15. use Psy\VersionUpdater\Installer;
  16. use Psy\VersionUpdater\SelfUpdate;
  17. use Symfony\Component\Console\Input\ArgvInput;
  18. use Symfony\Component\Console\Input\InputDefinition;
  19. use Symfony\Component\Console\Input\InputOption;
  20. use Symfony\Component\Console\Output\OutputInterface;
  21. /**
  22. * @group isolation-fail
  23. */
  24. class SelfUpdateTest extends \Psy\Test\TestCase
  25. {
  26. private function getSelfUpdater(Checker $checker, Installer $installer): SelfUpdate
  27. {
  28. $selfUpdate = new SelfUpdate($checker, $installer);
  29. $selfUpdate->setDownloader($this->getMockDownloader());
  30. return $selfUpdate;
  31. }
  32. public function testSuccessWhenCurrentVersionIsLatest()
  33. {
  34. $installer = $this->getMockInstaller();
  35. $checker = $this->getMockChecker(true);
  36. $output = $this->getMockOutput();
  37. $selfUpdate = $this->getSelfUpdater($checker, $installer);
  38. $returnValue = $selfUpdate->run($this->getInput(), $output);
  39. $this->assertEquals(SelfUpdate::SUCCESS, $returnValue);
  40. }
  41. public function testFailWhenCurrentVersionIsNotWritable()
  42. {
  43. $installer = $this->getMockInstaller(['isInstallLocationWritable']);
  44. $installer
  45. ->method('isInstallLocationWritable')
  46. ->willReturn(false);
  47. $checker = $this->getMockChecker();
  48. $output = $this->getMockOutput();
  49. $selfUpdate = $this->getSelfUpdater($checker, $installer);
  50. $returnValue = $selfUpdate->run($this->getInput(), $output);
  51. $this->assertEquals(SelfUpdate::FAILURE, $returnValue);
  52. }
  53. public function testFailWhenTempDirectoryIsNotWritable()
  54. {
  55. $installer = $this->getMockInstaller(['isTempDirectoryWritable']);
  56. $installer
  57. ->method('isTempDirectoryWritable')
  58. ->willReturn(false);
  59. $checker = $this->getMockChecker();
  60. $output = $this->getMockOutput();
  61. $selfUpdate = $this->getSelfUpdater($checker, $installer);
  62. $returnValue = $selfUpdate->run($this->getInput(), $output);
  63. $this->assertEquals(SelfUpdate::FAILURE, $returnValue);
  64. }
  65. public function testFailWhenDownloadingThrowsAnException()
  66. {
  67. $installer = $this->getMockInstaller();
  68. $checker = $this->getMockChecker();
  69. $output = $this->getMockOutput('TestCase Exception');
  70. $downloader = $this->getMockDownloader(['download']);
  71. $downloader
  72. ->method('download')
  73. ->willThrowException(new ErrorException('TestCase Exception'));
  74. $selfUpdate = $this->getSelfUpdater($checker, $installer);
  75. $selfUpdate->setDownloader($downloader);
  76. $returnValue = $selfUpdate->run($this->getInput(), $output);
  77. $this->assertEquals(SelfUpdate::FAILURE, $returnValue);
  78. }
  79. public function testFailWhenDownloadingFails()
  80. {
  81. $installer = $this->getMockInstaller();
  82. $checker = $this->getMockChecker();
  83. $output = $this->getMockOutput('Download failed.');
  84. $downloader = $this->getMockDownloader(['download']);
  85. $downloader
  86. ->method('download')
  87. ->willReturn(false);
  88. $selfUpdate = $this->getSelfUpdater($checker, $installer);
  89. $selfUpdate->setDownloader($downloader);
  90. $returnValue = $selfUpdate->run($this->getInput(), $output);
  91. $this->assertEquals(SelfUpdate::FAILURE, $returnValue);
  92. }
  93. public function testFailWhenDownloadedArchiveIsNotValid()
  94. {
  95. $installer = $this->getMockInstaller(['isValidSource']);
  96. $installer
  97. ->method('isValidSource')
  98. ->willReturn(false);
  99. $checker = $this->getMockChecker();
  100. $output = $this->getMockOutput('not a valid archive');
  101. $selfUpdate = $this->getSelfUpdater($checker, $installer);
  102. $returnValue = $selfUpdate->run($this->getInput(), $output);
  103. $this->assertEquals(SelfUpdate::FAILURE, $returnValue);
  104. }
  105. public function testFailWhenCreateBackupFails()
  106. {
  107. $installer = $this->getMockInstaller(['createBackup']);
  108. $installer
  109. ->method('createBackup')
  110. ->willReturn(false);
  111. $checker = $this->getMockChecker();
  112. $output = $this->getMockOutput('Failed to create a backup');
  113. $selfUpdate = $this->getSelfUpdater($checker, $installer);
  114. $returnValue = $selfUpdate->run($this->getInput(), $output);
  115. $this->assertEquals(SelfUpdate::FAILURE, $returnValue);
  116. }
  117. public function testBackupIsRestoredWhenInstallFails()
  118. {
  119. $installer = $this->getMockInstaller(['install', 'restoreFromBackup']);
  120. $installer
  121. ->method('install')
  122. ->willReturn(false);
  123. $installer
  124. ->expects($this->once())
  125. ->method('restoreFromBackup');
  126. $checker = $this->getMockChecker();
  127. $output = $this->getMockOutput();
  128. $selfUpdate = $this->getSelfUpdater($checker, $installer);
  129. $returnValue = $selfUpdate->run($this->getInput(), $output);
  130. $this->assertEquals(SelfUpdate::FAILURE, $returnValue);
  131. }
  132. public function testExceptionIsNotCaughtWhenRestoreFails()
  133. {
  134. $this->expectException(ErrorException::class);
  135. $installer = $this->getMockInstaller(['restoreFromBackup', 'install']);
  136. $installer
  137. ->method('install')
  138. ->willReturn(false);
  139. $installer
  140. ->method('restoreFromBackup')
  141. ->willThrowException(new ErrorException('Uncaught Exception'));
  142. $checker = $this->getMockChecker();
  143. $output = $this->getMockOutput();
  144. $selfUpdate = $this->getSelfUpdater($checker, $installer);
  145. $selfUpdate->run($this->getInput(), $output);
  146. $this->fail('Expected ErrorException not thrown');
  147. }
  148. private function getInput()
  149. {
  150. $input = new ArgvInput([]);
  151. // build a simple input with options that are used in SelfUpdate
  152. $input->bind(new InputDefinition([
  153. new InputOption('verbose', 'v', InputOption::VALUE_NONE),
  154. ]));
  155. return $input;
  156. }
  157. /**
  158. * Use the more strict onlyMethods if it's available, otherwise use the deprecated setMethods.
  159. *
  160. * @return void
  161. */
  162. private function setMockMethods($mockBuilder, array $methods)
  163. {
  164. if (\method_exists($mockBuilder, 'onlyMethods')) {
  165. $mockBuilder->onlyMethods($methods);
  166. } else {
  167. $mockBuilder->setMethods($methods);
  168. }
  169. }
  170. private function getMockChecker(bool $isLatest = false, string $version = Shell::VERSION)
  171. {
  172. $builder = $this->getMockBuilder(Checker::class);
  173. $this->setMockMethods($builder, ['getLatest', 'isLatest']);
  174. $checker = $builder->getMock();
  175. $checker->method('getLatest')->willReturn($version);
  176. $checker->method('isLatest')->willReturn($isLatest);
  177. return $checker;
  178. }
  179. private function getMockInstaller(array $skipMethods = [])
  180. {
  181. $methods = \get_class_methods(Installer::class);
  182. $builder = $this->getMockBuilder(Installer::class);
  183. $this->setMockMethods($builder, $methods);
  184. $installer = $builder->getMock();
  185. $skipMethods = \array_merge($skipMethods, ['getTempDirectory', 'getBackupFilename', '__construct']);
  186. foreach ($methods as $method) {
  187. if (!\in_array($method, $skipMethods)) {
  188. $installer->method($method)->willReturn(true);
  189. }
  190. }
  191. return $installer;
  192. }
  193. private function getMockDownloader(array $skipMethods = [])
  194. {
  195. $methods = \get_class_methods(Downloader::class);
  196. $builder = $this->getMockBuilder(Downloader::class);
  197. $this->setMockMethods($builder, $methods);
  198. $downloader = $builder->getMock();
  199. $skipMethods = \array_merge($skipMethods, ['getFilename', '__construct']);
  200. foreach ($methods as $method) {
  201. if (!\in_array($method, $skipMethods)) {
  202. $downloader->method($method)->willReturn(true);
  203. }
  204. }
  205. return $downloader;
  206. }
  207. private function getMockOutput(string $expectOutput = null)
  208. {
  209. $methods = \get_class_methods(OutputInterface::class);
  210. $builder = $this->getMockBuilder(OutputInterface::class);
  211. $this->setMockMethods($builder, $methods);
  212. $output = $builder->getMock();
  213. if ($expectOutput) {
  214. $output
  215. ->expects($this->atLeastOnce())
  216. ->method('writeln')
  217. ->with(
  218. $this->stringContains($expectOutput)
  219. );
  220. }
  221. return $output;
  222. }
  223. }