JSCombinerTest.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. <?php
  2. /**
  3. * @copyright 2017, Roeland Jago Douma <roeland@famdouma.nl>
  4. *
  5. * @author Roeland Jago Douma <roeland@famdouma.nl>
  6. *
  7. * @license GNU AGPL version 3 or any later version
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License as
  11. * published by the Free Software Foundation, either version 3 of the
  12. * License, or (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU Affero General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public License
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. *
  22. */
  23. namespace Test\Template;
  24. use OC\SystemConfig;
  25. use OC\Template\JSCombiner;
  26. use OCP\Files\IAppData;
  27. use OCP\Files\NotFoundException;
  28. use OCP\Files\NotPermittedException;
  29. use OCP\Files\SimpleFS\ISimpleFile;
  30. use OCP\Files\SimpleFS\ISimpleFolder;
  31. use OCP\ICache;
  32. use OCP\ICacheFactory;
  33. use OCP\ILogger;
  34. use OCP\IURLGenerator;
  35. class JSCombinerTest extends \Test\TestCase {
  36. /** @var IAppData|\PHPUnit\Framework\MockObject\MockObject */
  37. protected $appData;
  38. /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */
  39. protected $urlGenerator;
  40. /** @var SystemConfig|\PHPUnit\Framework\MockObject\MockObject */
  41. protected $config;
  42. /** @var ICache|\PHPUnit\Framework\MockObject\MockObject */
  43. protected $depsCache;
  44. /** @var JSCombiner */
  45. protected $jsCombiner;
  46. /** @var ILogger|\PHPUnit\Framework\MockObject\MockObject */
  47. protected $logger;
  48. /** @var ICacheFactory|\PHPUnit\Framework\MockObject\MockObject */
  49. protected $cacheFactory;
  50. protected function setUp(): void {
  51. parent::setUp();
  52. $this->appData = $this->createMock(IAppData::class);
  53. $this->urlGenerator = $this->createMock(IURLGenerator::class);
  54. $this->config = $this->createMock(SystemConfig::class);
  55. $this->cacheFactory = $this->createMock(ICacheFactory::class);
  56. $this->depsCache = $this->createMock(ICache::class);
  57. $this->cacheFactory->expects($this->at(0))
  58. ->method('createDistributed')
  59. ->willReturn($this->depsCache);
  60. $this->logger = $this->createMock(ILogger::class);
  61. $this->jsCombiner = new JSCombiner(
  62. $this->appData,
  63. $this->urlGenerator,
  64. $this->cacheFactory,
  65. $this->config,
  66. $this->logger
  67. );
  68. }
  69. public function testProcessDebugMode() {
  70. $this->config
  71. ->expects($this->once())
  72. ->method('getValue')
  73. ->with('debug')
  74. ->willReturn(true);
  75. $actual = $this->jsCombiner->process(__DIR__, '/data/combine.json', 'awesomeapp');
  76. $this->assertFalse($actual);
  77. }
  78. public function testProcessNotInstalled() {
  79. $this->config
  80. ->expects($this->at(0))
  81. ->method('getValue')
  82. ->with('debug')
  83. ->willReturn(false);
  84. $this->config
  85. ->expects($this->at(1))
  86. ->method('getValue')
  87. ->with('installed')
  88. ->willReturn(false);
  89. $actual = $this->jsCombiner->process(__DIR__, '/data/combine.json', 'awesomeapp');
  90. $this->assertFalse($actual);
  91. }
  92. public function testProcessUncachedFileNoAppDataFolder() {
  93. $this->config
  94. ->expects($this->at(0))
  95. ->method('getValue')
  96. ->with('debug')
  97. ->willReturn(false);
  98. $this->config
  99. ->expects($this->at(1))
  100. ->method('getValue')
  101. ->with('installed')
  102. ->willReturn(true);
  103. $folder = $this->createMock(ISimpleFolder::class);
  104. $this->appData->expects($this->once())->method('getFolder')->with('awesomeapp')->willThrowException(new NotFoundException());
  105. $this->appData->expects($this->once())->method('newFolder')->with('awesomeapp')->willReturn($folder);
  106. $file = $this->createMock(ISimpleFile::class);
  107. $gzfile = $this->createMock(ISimpleFile::class);
  108. $fileDeps = $this->createMock(ISimpleFile::class);
  109. $folder->method('getFile')
  110. ->willReturnCallback(function ($path) use ($file, $gzfile) {
  111. if ($path === 'combine.js') {
  112. return $file;
  113. } elseif ($path === 'combine.js.deps') {
  114. throw new NotFoundException();
  115. } elseif ($path === 'combine.js.gzip') {
  116. return $gzfile;
  117. }
  118. $this->fail();
  119. });
  120. $folder->expects($this->once())
  121. ->method('newFile')
  122. ->with('combine.js.deps')
  123. ->willReturn($fileDeps);
  124. $actual = $this->jsCombiner->process(__DIR__, '/data/combine.json', 'awesomeapp');
  125. $this->assertTrue($actual);
  126. }
  127. public function testProcessUncachedFile() {
  128. $this->config
  129. ->expects($this->at(0))
  130. ->method('getValue')
  131. ->with('debug')
  132. ->willReturn(false);
  133. $this->config
  134. ->expects($this->at(1))
  135. ->method('getValue')
  136. ->with('installed')
  137. ->willReturn(true);
  138. $folder = $this->createMock(ISimpleFolder::class);
  139. $this->appData->expects($this->once())->method('getFolder')->with('awesomeapp')->willReturn($folder);
  140. $file = $this->createMock(ISimpleFile::class);
  141. $fileDeps = $this->createMock(ISimpleFile::class);
  142. $gzfile = $this->createMock(ISimpleFile::class);
  143. $folder->method('getFile')
  144. ->willReturnCallback(function ($path) use ($file, $gzfile) {
  145. if ($path === 'combine.js') {
  146. return $file;
  147. } elseif ($path === 'combine.js.deps') {
  148. throw new NotFoundException();
  149. } elseif ($path === 'combine.js.gzip') {
  150. return $gzfile;
  151. }
  152. $this->fail();
  153. });
  154. $folder->expects($this->once())
  155. ->method('newFile')
  156. ->with('combine.js.deps')
  157. ->willReturn($fileDeps);
  158. $actual = $this->jsCombiner->process(__DIR__, '/data/combine.json', 'awesomeapp');
  159. $this->assertTrue($actual);
  160. }
  161. public function testProcessCachedFile() {
  162. $this->config
  163. ->expects($this->at(0))
  164. ->method('getValue')
  165. ->with('debug')
  166. ->willReturn(false);
  167. $this->config
  168. ->expects($this->at(1))
  169. ->method('getValue')
  170. ->with('installed')
  171. ->willReturn(true);
  172. $folder = $this->createMock(ISimpleFolder::class);
  173. $this->appData->expects($this->once())->method('getFolder')->with('awesomeapp')->willReturn($folder);
  174. $file = $this->createMock(ISimpleFile::class);
  175. $fileDeps = $this->createMock(ISimpleFile::class);
  176. $fileDeps->expects($this->once())->method('getContent')->willReturn('{}');
  177. $folder->method('fileExists')
  178. ->with('combine.js')
  179. ->willReturn(true);
  180. $folder->method('getFile')
  181. ->willReturnCallback(function ($path) use ($file, $fileDeps) {
  182. if ($path === 'combine.js') {
  183. return $file;
  184. }
  185. if ($path === 'combine.js.deps') {
  186. return $fileDeps;
  187. }
  188. $this->fail();
  189. });
  190. $actual = $this->jsCombiner->process(__DIR__, '/data/combine.json', 'awesomeapp');
  191. $this->assertTrue($actual);
  192. }
  193. public function testProcessCachedFileMemcache() {
  194. $this->config
  195. ->expects($this->at(0))
  196. ->method('getValue')
  197. ->with('debug')
  198. ->willReturn(false);
  199. $this->config
  200. ->expects($this->at(1))
  201. ->method('getValue')
  202. ->with('installed')
  203. ->willReturn(true);
  204. $folder = $this->createMock(ISimpleFolder::class);
  205. $this->appData->expects($this->once())
  206. ->method('getFolder')
  207. ->with('awesomeapp')
  208. ->willReturn($folder);
  209. $folder->method('getName')
  210. ->willReturn('awesomeapp');
  211. $folder->method('fileExists')
  212. ->with('combine.js')
  213. ->willReturn(true);
  214. $file = $this->createMock(ISimpleFile::class);
  215. $this->depsCache->method('get')
  216. ->with('awesomeapp-combine.js.deps')
  217. ->willReturn('{}');
  218. $folder->method('getFile')
  219. ->willReturnCallback(function ($path) use ($file) {
  220. if ($path === 'combine.js') {
  221. return $file;
  222. }
  223. $this->fail();
  224. });
  225. $actual = $this->jsCombiner->process(__DIR__, '/data/combine.json', 'awesomeapp');
  226. $this->assertTrue($actual);
  227. }
  228. public function testIsCachedNoDepsFile() {
  229. $fileName = 'combine.json';
  230. $folder = $this->createMock(ISimpleFolder::class);
  231. $file = $this->createMock(ISimpleFile::class);
  232. $folder->method('getFile')
  233. ->willReturnCallback(function ($path) use ($file) {
  234. if ($path === 'combine.js') {
  235. return $file;
  236. }
  237. if ($path === 'combine.js.deps') {
  238. throw new NotFoundException();
  239. }
  240. $this->fail();
  241. });
  242. $actual = self::invokePrivate($this->jsCombiner, 'isCached', [$fileName, $folder]);
  243. $this->assertFalse($actual);
  244. }
  245. public function testIsCachedWithNotExistingFile() {
  246. $fileName = 'combine.json';
  247. $folder = $this->createMock(ISimpleFolder::class);
  248. $folder->method('fileExists')
  249. ->with('combine.js')
  250. ->willReturn(true);
  251. $file = $this->createMock(ISimpleFile::class);
  252. $folder->method('getFile')
  253. ->with('combine.js.deps')
  254. ->willReturn($file);
  255. $file->expects($this->once())
  256. ->method('getContent')
  257. ->willReturn(json_encode(['/etc/certainlynotexisting/file/ihope' => 10000]));
  258. $actual = self::invokePrivate($this->jsCombiner, 'isCached', [$fileName, $folder]);
  259. $this->assertFalse($actual);
  260. }
  261. public function testIsCachedWithOlderMtime() {
  262. $fileName = 'combine.json';
  263. $folder = $this->createMock(ISimpleFolder::class);
  264. $folder->method('fileExists')
  265. ->with('combine.js')
  266. ->willReturn(true);
  267. $file = $this->createMock(ISimpleFile::class);
  268. $folder->method('getFile')
  269. ->with('combine.js.deps')
  270. ->willReturn($file);
  271. $file->expects($this->once())
  272. ->method('getContent')
  273. ->willReturn(json_encode([__FILE__ => 1234]));
  274. $actual = self::invokePrivate($this->jsCombiner, 'isCached', [$fileName, $folder]);
  275. $this->assertFalse($actual);
  276. }
  277. public function testIsCachedWithoutContent() {
  278. $fileName = 'combine.json';
  279. $folder = $this->createMock(ISimpleFolder::class);
  280. $folder->method('fileExists')
  281. ->with('combine.js')
  282. ->willReturn(true);
  283. $file = $this->createMock(ISimpleFile::class);
  284. $folder->method('getFile')
  285. ->with('combine.js.deps')
  286. ->willReturn($file);
  287. $file->expects($this->once())
  288. ->method('getContent')
  289. ->willReturn('');
  290. $this->logger->expects($this->once())
  291. ->method('info')
  292. ->with('JSCombiner: deps file empty: combine.js.deps');
  293. $actual = self::invokePrivate($this->jsCombiner, 'isCached', [$fileName, $folder]);
  294. $this->assertFalse($actual);
  295. }
  296. public function testCacheNoFile() {
  297. $fileName = 'combine.js';
  298. $folder = $this->createMock(ISimpleFolder::class);
  299. $file = $this->createMock(ISimpleFile::class);
  300. $depsFile = $this->createMock(ISimpleFile::class);
  301. $gzFile = $this->createMock(ISimpleFile::class);
  302. $path = __DIR__ . '/data/';
  303. $folder->method('getFile')->willThrowException(new NotFoundException());
  304. $folder->method('newFile')->willReturnCallback(
  305. function ($filename) use ($file, $depsFile, $gzFile) {
  306. if ($filename === 'combine.js') {
  307. return $file;
  308. } elseif ($filename === 'combine.js.deps') {
  309. return $depsFile;
  310. } elseif ($filename === 'combine.js.gzip') {
  311. return $gzFile;
  312. }
  313. $this->fail();
  314. }
  315. );
  316. $file->expects($this->once())->method('putContent');
  317. $depsFile->expects($this->once())->method('putContent');
  318. $gzFile->expects($this->once())->method('putContent');
  319. $actual = self::invokePrivate($this->jsCombiner, 'cache', [$path, 'combine.json', $folder]);
  320. $this->assertTrue($actual);
  321. }
  322. public function testCache() {
  323. $fileName = 'combine.js';
  324. $folder = $this->createMock(ISimpleFolder::class);
  325. $file = $this->createMock(ISimpleFile::class);
  326. $depsFile = $this->createMock(ISimpleFile::class);
  327. $gzFile = $this->createMock(ISimpleFile::class);
  328. $path = __DIR__ . '/data/';
  329. $folder->method('getFile')->willReturnCallback(
  330. function ($filename) use ($file, $depsFile, $gzFile) {
  331. if ($filename === 'combine.js') {
  332. return $file;
  333. } elseif ($filename === 'combine.js.deps') {
  334. return $depsFile;
  335. } elseif ($filename === 'combine.js.gzip') {
  336. return $gzFile;
  337. }
  338. $this->fail();
  339. }
  340. );
  341. $file->expects($this->once())->method('putContent');
  342. $depsFile->expects($this->once())->method('putContent');
  343. $gzFile->expects($this->once())->method('putContent');
  344. $actual = self::invokePrivate($this->jsCombiner, 'cache', [$path, 'combine.json', $folder]);
  345. $this->assertTrue($actual);
  346. }
  347. public function testCacheNotPermittedException() {
  348. $fileName = 'combine.js';
  349. $folder = $this->createMock(ISimpleFolder::class);
  350. $file = $this->createMock(ISimpleFile::class);
  351. $depsFile = $this->createMock(ISimpleFile::class);
  352. $path = __DIR__ . '/data/';
  353. $folder->expects($this->at(0))->method('getFile')->with($fileName)->willReturn($file);
  354. $folder->expects($this->at(1))->method('getFile')->with($fileName . '.deps')->willReturn($depsFile);
  355. $file->expects($this->at(0))
  356. ->method('putContent')
  357. ->with('var a = \'hello\';
  358. var b = \'world\';
  359. ');
  360. $depsFile
  361. ->expects($this->at(0))
  362. ->method('putContent')
  363. ->with($this->callback(
  364. function ($content) {
  365. $deps = json_decode($content, true);
  366. return array_key_exists(__DIR__ . '/data//1.js', $deps)
  367. && array_key_exists(__DIR__ . '/data//2.js', $deps);
  368. }))
  369. ->willThrowException(new NotPermittedException());
  370. $actual = self::invokePrivate($this->jsCombiner, 'cache', [$path, 'combine.json', $folder]);
  371. $this->assertFalse($actual);
  372. }
  373. public function testCacheSuccess() {
  374. $fileName = 'combine.js';
  375. $folder = $this->createMock(ISimpleFolder::class);
  376. $file = $this->createMock(ISimpleFile::class);
  377. $depsFile = $this->createMock(ISimpleFile::class);
  378. $gzFile = $this->createMock(ISimpleFile::class);
  379. $path = __DIR__ . '/data/';
  380. $folder->method('getFile')->willReturnCallback(
  381. function ($filename) use ($file, $depsFile, $gzFile) {
  382. if ($filename === 'combine.js') {
  383. return $file;
  384. } elseif ($filename === 'combine.js.deps') {
  385. return $depsFile;
  386. } elseif ($filename === 'combine.js.gzip') {
  387. return $gzFile;
  388. }
  389. $this->fail();
  390. }
  391. );
  392. $file->expects($this->at(0))
  393. ->method('putContent')
  394. ->with('var a = \'hello\';
  395. var b = \'world\';
  396. ');
  397. $depsFile->expects($this->at(0))->method('putContent')->with($this->callback(
  398. function ($content) {
  399. $deps = json_decode($content, true);
  400. return array_key_exists(__DIR__ . '/data//1.js', $deps)
  401. && array_key_exists(__DIR__ . '/data//2.js', $deps);
  402. }));
  403. $gzFile->expects($this->at(0))->method('putContent')->with($this->callback(
  404. function ($content) {
  405. return gzdecode($content) === 'var a = \'hello\';
  406. var b = \'world\';
  407. ';
  408. }
  409. ));
  410. $actual = self::invokePrivate($this->jsCombiner, 'cache', [$path, 'combine.json', $folder]);
  411. $this->assertTrue($actual);
  412. }
  413. public function dataGetCachedSCSS() {
  414. return [
  415. ['awesomeapp', 'core/js/foo.json', '/js/core/foo.js'],
  416. ['files', 'apps/files/js/foo.json', '/js/files/foo.js']
  417. ];
  418. }
  419. /**
  420. * @param $appName
  421. * @param $fileName
  422. * @param $result
  423. * @dataProvider dataGetCachedSCSS
  424. */
  425. public function testGetCachedSCSS($appName, $fileName, $result) {
  426. $this->urlGenerator->expects($this->once())
  427. ->method('linkToRoute')
  428. ->with('core.Js.getJs', [
  429. 'fileName' => 'foo.js',
  430. 'appName' => $appName
  431. ])
  432. ->willReturn(\OC::$WEBROOT . $result);
  433. $actual = $this->jsCombiner->getCachedJS($appName, $fileName);
  434. $this->assertEquals(substr($result, 1), $actual);
  435. }
  436. public function testGetContent() {
  437. // Create temporary file with some content
  438. $tmpFile = \OC::$server->getTempManager()->getTemporaryFile('JSCombinerTest');
  439. $pathInfo = pathinfo($tmpFile);
  440. file_put_contents($tmpFile, json_encode(['/foo/bar/test', $pathInfo['dirname'] . '/js/mytest.js']));
  441. $tmpFilePathArray = explode('/', $pathInfo['basename']);
  442. array_pop($tmpFilePathArray);
  443. $expected = [
  444. '//foo/bar/test',
  445. '/' . implode('/', $tmpFilePathArray) . $pathInfo['dirname'] . '/js/mytest.js',
  446. ];
  447. $this->assertEquals($expected, $this->jsCombiner->getContent($pathInfo['dirname'], $pathInfo['basename']));
  448. }
  449. public function testGetContentInvalidJson() {
  450. // Create temporary file with some content
  451. $tmpFile = \OC::$server->getTempManager()->getTemporaryFile('JSCombinerTest');
  452. $pathInfo = pathinfo($tmpFile);
  453. file_put_contents($tmpFile, 'CertainlyNotJson');
  454. $expected = [];
  455. $this->assertEquals($expected, $this->jsCombiner->getContent($pathInfo['dirname'], $pathInfo['basename']));
  456. }
  457. public function testResetCache() {
  458. $file = $this->createMock(ISimpleFile::class);
  459. $file->expects($this->once())
  460. ->method('delete');
  461. $folder = $this->createMock(ISimpleFolder::class);
  462. $folder->expects($this->once())
  463. ->method('getDirectoryListing')
  464. ->willReturn([$file]);
  465. $cache = $this->createMock(ICache::class);
  466. $this->cacheFactory->expects($this->once())
  467. ->method('createDistributed')
  468. ->willReturn($cache);
  469. $cache->expects($this->once())
  470. ->method('clear')
  471. ->with('');
  472. $this->appData->expects($this->once())
  473. ->method('getDirectoryListing')
  474. ->willReturn([$folder]);
  475. $this->jsCombiner->resetCache();
  476. }
  477. }