ChangesCheckTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace Test\Updater;
  8. use OC\Updater\Changes;
  9. use OC\Updater\ChangesCheck;
  10. use OC\Updater\ChangesMapper;
  11. use OCP\AppFramework\Db\DoesNotExistException;
  12. use OCP\Http\Client\IClient;
  13. use OCP\Http\Client\IClientService;
  14. use OCP\Http\Client\IResponse;
  15. use Psr\Log\LoggerInterface;
  16. use Test\TestCase;
  17. class ChangesCheckTest extends TestCase {
  18. /** @var IClientService|\PHPUnit\Framework\MockObject\MockObject */
  19. protected $clientService;
  20. /** @var ChangesCheck */
  21. protected $checker;
  22. /** @var ChangesMapper|\PHPUnit\Framework\MockObject\MockObject */
  23. protected $mapper;
  24. /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
  25. protected $logger;
  26. protected function setUp(): void {
  27. parent::setUp();
  28. $this->clientService = $this->createMock(IClientService::class);
  29. $this->mapper = $this->createMock(ChangesMapper::class);
  30. $this->logger = $this->createMock(LoggerInterface::class);
  31. $this->checker = new ChangesCheck($this->clientService, $this->mapper, $this->logger);
  32. }
  33. public function statusCodeProvider():array {
  34. return [
  35. [200, ChangesCheck::RESPONSE_HAS_CONTENT],
  36. [304, ChangesCheck::RESPONSE_USE_CACHE],
  37. [404, ChangesCheck::RESPONSE_NO_CONTENT],
  38. [418, ChangesCheck::RESPONSE_NO_CONTENT],
  39. ];
  40. }
  41. /**
  42. * @dataProvider statusCodeProvider
  43. */
  44. public function testEvaluateResponse(int $statusCode, int $expected) {
  45. $response = $this->createMock(IResponse::class);
  46. $response->expects($this->atLeastOnce())
  47. ->method('getStatusCode')
  48. ->willReturn($statusCode);
  49. if (!in_array($statusCode, [200, 304, 404])) {
  50. $this->logger->expects($this->once())
  51. ->method('debug');
  52. }
  53. $evaluation = $this->invokePrivate($this->checker, 'evaluateResponse', [$response]);
  54. $this->assertSame($expected, $evaluation);
  55. }
  56. public function testCacheResultInsert() {
  57. $version = '13.0.4';
  58. $entry = $this->createMock(Changes::class);
  59. $entry->expects($this->exactly(2))
  60. ->method('__call')
  61. ->withConsecutive(['getVersion'], ['setVersion', [$version]])
  62. ->willReturnOnConsecutiveCalls('', null);
  63. $this->mapper->expects($this->once())
  64. ->method('insert');
  65. $this->mapper->expects($this->never())
  66. ->method('update');
  67. $this->invokePrivate($this->checker, 'cacheResult', [$entry, $version]);
  68. }
  69. public function testCacheResultUpdate() {
  70. $version = '13.0.4';
  71. $entry = $this->createMock(Changes::class);
  72. $entry->expects($this->once())
  73. ->method('__call')
  74. ->willReturn($version);
  75. $this->mapper->expects($this->never())
  76. ->method('insert');
  77. $this->mapper->expects($this->once())
  78. ->method('update');
  79. $this->invokePrivate($this->checker, 'cacheResult', [$entry, $version]);
  80. }
  81. public function changesXMLProvider(): array {
  82. return [
  83. [ # 0 - full example
  84. '<?xml version="1.0" encoding="utf-8" ?>
  85. <release xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  86. xsi:noNamespaceSchemaLocation="https://updates.nextcloud.com/changelog_server/schema.xsd"
  87. version="13.0.0">
  88. <changelog href="https://nextcloud.com/changelog/#13-0-0"/>
  89. <whatsNew lang="en">
  90. <regular>
  91. <item>Refined user interface</item>
  92. <item>End-to-end Encryption</item>
  93. <item>Video and Text Chat</item>
  94. </regular>
  95. <admin>
  96. <item>Changes to the Nginx configuration</item>
  97. <item>Theming: CSS files were consolidated</item>
  98. </admin>
  99. </whatsNew>
  100. <whatsNew lang="de">
  101. <regular>
  102. <item>Überarbeitete Benutzerschnittstelle</item>
  103. <item>Ende-zu-Ende Verschlüsselung</item>
  104. <item>Video- und Text-Chat</item>
  105. </regular>
  106. <admin>
  107. <item>Änderungen an der Nginx Konfiguration</item>
  108. <item>Theming: CSS Dateien wurden konsolidiert</item>
  109. </admin>
  110. </whatsNew>
  111. </release>',
  112. [
  113. 'changelogURL' => 'https://nextcloud.com/changelog/#13-0-0',
  114. 'whatsNew' => [
  115. 'en' => [
  116. 'regular' => [
  117. 'Refined user interface',
  118. 'End-to-end Encryption',
  119. 'Video and Text Chat'
  120. ],
  121. 'admin' => [
  122. 'Changes to the Nginx configuration',
  123. 'Theming: CSS files were consolidated'
  124. ],
  125. ],
  126. 'de' => [
  127. 'regular' => [
  128. 'Überarbeitete Benutzerschnittstelle',
  129. 'Ende-zu-Ende Verschlüsselung',
  130. 'Video- und Text-Chat'
  131. ],
  132. 'admin' => [
  133. 'Änderungen an der Nginx Konfiguration',
  134. 'Theming: CSS Dateien wurden konsolidiert'
  135. ],
  136. ],
  137. ],
  138. ]
  139. ],
  140. [ # 1- admin part not translated
  141. '<?xml version="1.0" encoding="utf-8" ?>
  142. <release xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  143. xsi:noNamespaceSchemaLocation="https://updates.nextcloud.com/changelog_server/schema.xsd"
  144. version="13.0.0">
  145. <changelog href="https://nextcloud.com/changelog/#13-0-0"/>
  146. <whatsNew lang="en">
  147. <regular>
  148. <item>Refined user interface</item>
  149. <item>End-to-end Encryption</item>
  150. <item>Video and Text Chat</item>
  151. </regular>
  152. <admin>
  153. <item>Changes to the Nginx configuration</item>
  154. <item>Theming: CSS files were consolidated</item>
  155. </admin>
  156. </whatsNew>
  157. <whatsNew lang="de">
  158. <regular>
  159. <item>Überarbeitete Benutzerschnittstelle</item>
  160. <item>Ende-zu-Ende Verschlüsselung</item>
  161. <item>Video- und Text-Chat</item>
  162. </regular>
  163. </whatsNew>
  164. </release>',
  165. [
  166. 'changelogURL' => 'https://nextcloud.com/changelog/#13-0-0',
  167. 'whatsNew' => [
  168. 'en' => [
  169. 'regular' => [
  170. 'Refined user interface',
  171. 'End-to-end Encryption',
  172. 'Video and Text Chat'
  173. ],
  174. 'admin' => [
  175. 'Changes to the Nginx configuration',
  176. 'Theming: CSS files were consolidated'
  177. ],
  178. ],
  179. 'de' => [
  180. 'regular' => [
  181. 'Überarbeitete Benutzerschnittstelle',
  182. 'Ende-zu-Ende Verschlüsselung',
  183. 'Video- und Text-Chat'
  184. ],
  185. 'admin' => [
  186. ],
  187. ],
  188. ],
  189. ]
  190. ],
  191. [ # 2 - minimal set
  192. '<?xml version="1.0" encoding="utf-8" ?>
  193. <release xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  194. xsi:noNamespaceSchemaLocation="https://updates.nextcloud.com/changelog_server/schema.xsd"
  195. version="13.0.0">
  196. <changelog href="https://nextcloud.com/changelog/#13-0-0"/>
  197. <whatsNew lang="en">
  198. <regular>
  199. <item>Refined user interface</item>
  200. <item>End-to-end Encryption</item>
  201. <item>Video and Text Chat</item>
  202. </regular>
  203. </whatsNew>
  204. </release>',
  205. [
  206. 'changelogURL' => 'https://nextcloud.com/changelog/#13-0-0',
  207. 'whatsNew' => [
  208. 'en' => [
  209. 'regular' => [
  210. 'Refined user interface',
  211. 'End-to-end Encryption',
  212. 'Video and Text Chat'
  213. ],
  214. 'admin' => [],
  215. ],
  216. ],
  217. ]
  218. ],
  219. [ # 3 - minimal set (procrastinator edition)
  220. '<?xml version="1.0" encoding="utf-8" ?>
  221. <release xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  222. xsi:noNamespaceSchemaLocation="https://updates.nextcloud.com/changelog_server/schema.xsd"
  223. version="13.0.0">
  224. <changelog href="https://nextcloud.com/changelog/#13-0-0"/>
  225. <whatsNew lang="en">
  226. <regular>
  227. <item>Write this tomorrow</item>
  228. </regular>
  229. </whatsNew>
  230. </release>',
  231. [
  232. 'changelogURL' => 'https://nextcloud.com/changelog/#13-0-0',
  233. 'whatsNew' => [
  234. 'en' => [
  235. 'regular' => [
  236. 'Write this tomorrow',
  237. ],
  238. 'admin' => [],
  239. ],
  240. ],
  241. ]
  242. ],
  243. [ # 4 - empty
  244. '',
  245. []
  246. ],
  247. ];
  248. }
  249. /**
  250. * @dataProvider changesXMLProvider
  251. */
  252. public function testExtractData(string $body, array $expected) {
  253. $actual = $this->invokePrivate($this->checker, 'extractData', [$body]);
  254. $this->assertSame($expected, $actual);
  255. }
  256. public function etagProvider() {
  257. return [
  258. [''],
  259. ['a27aab83d8205d73978435076e53d143']
  260. ];
  261. }
  262. /**
  263. * @dataProvider etagProvider
  264. */
  265. public function testQueryChangesServer(string $etag) {
  266. $uri = 'https://changes.nextcloud.server/?13.0.5';
  267. $entry = $this->createMock(Changes::class);
  268. $entry->expects($this->any())
  269. ->method('__call')
  270. ->willReturn($etag);
  271. $expectedHeaders = $etag === '' ? [] : ['If-None-Match' => [$etag]];
  272. $client = $this->createMock(IClient::class);
  273. $client->expects($this->once())
  274. ->method('get')
  275. ->with($uri, ['headers' => $expectedHeaders])
  276. ->willReturn($this->createMock(IResponse::class));
  277. $this->clientService->expects($this->once())
  278. ->method('newClient')
  279. ->willReturn($client);
  280. $response = $this->invokePrivate($this->checker, 'queryChangesServer', [$uri, $entry]);
  281. $this->assertInstanceOf(IResponse::class, $response);
  282. }
  283. public function versionProvider(): array {
  284. return [
  285. ['13.0.7', '13.0.7'],
  286. ['13.0.7.3', '13.0.7'],
  287. ['13.0.7.3.42', '13.0.7'],
  288. ['13.0', '13.0.0'],
  289. ['13', '13.0.0'],
  290. ['', '0.0.0'],
  291. ];
  292. }
  293. /**
  294. * @dataProvider versionProvider
  295. */
  296. public function testNormalizeVersion(string $input, string $expected) {
  297. $normalized = $this->checker->normalizeVersion($input);
  298. $this->assertSame($expected, $normalized);
  299. }
  300. public function changeDataProvider():array {
  301. $testDataFound = $testDataNotFound = $this->versionProvider();
  302. array_walk($testDataFound, function (&$params) {
  303. $params[] = true;
  304. });
  305. array_walk($testDataNotFound, function (&$params) {
  306. $params[] = false;
  307. });
  308. return array_merge($testDataFound, $testDataNotFound);
  309. }
  310. /**
  311. * @dataProvider changeDataProvider
  312. *
  313. */
  314. public function testGetChangesForVersion(string $inputVersion, string $normalizedVersion, bool $isFound) {
  315. $mocker = $this->mapper->expects($this->once())
  316. ->method('getChanges')
  317. ->with($normalizedVersion);
  318. if (!$isFound) {
  319. $this->expectException(DoesNotExistException::class);
  320. $mocker->willThrowException(new DoesNotExistException('Changes info is not present'));
  321. } else {
  322. $entry = $this->createMock(Changes::class);
  323. $entry->expects($this->once())
  324. ->method('__call')
  325. ->with('getData')
  326. ->willReturn('{"changelogURL":"https:\/\/nextcloud.com\/changelog\/#13-0-0","whatsNew":{"en":{"regular":["Refined user interface","End-to-end Encryption","Video and Text Chat"],"admin":["Changes to the Nginx configuration","Theming: CSS files were consolidated"]},"de":{"regular":["\u00dcberarbeitete Benutzerschnittstelle","Ende-zu-Ende Verschl\u00fcsselung","Video- und Text-Chat"],"admin":["\u00c4nderungen an der Nginx Konfiguration","Theming: CSS Dateien wurden konsolidiert"]}}}');
  327. $mocker->willReturn($entry);
  328. }
  329. /** @noinspection PhpUnhandledExceptionInspection */
  330. $data = $this->checker->getChangesForVersion($inputVersion);
  331. $this->assertTrue(isset($data['whatsNew']['en']['regular']));
  332. $this->assertTrue(isset($data['changelogURL']));
  333. }
  334. public function testGetChangesForVersionEmptyData() {
  335. $entry = $this->createMock(Changes::class);
  336. $entry->expects($this->once())
  337. ->method('__call')
  338. ->with('getData')
  339. ->willReturn('');
  340. $this->mapper->expects($this->once())
  341. ->method('getChanges')
  342. ->with('13.0.7')
  343. ->willReturn($entry);
  344. $this->expectException(DoesNotExistException::class);
  345. /** @noinspection PhpUnhandledExceptionInspection */
  346. $this->checker->getChangesForVersion('13.0.7');
  347. }
  348. }