From 54f9b35f71cec60880b46f5684c3a2115a0e9cda Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Sun, 26 Mar 2017 16:30:08 +0200 Subject: [PATCH 1/3] Allow to gzip CSS/JS files Since in production the SCSS files are compiled once and the javascript files are combined once we can just as well gzip them aggresively. This means that once they are requested and the browser supports gzip we can just serve the gzipped file saving precious bandwidth. Signed-off-by: Roeland Jago Douma --- core/Controller/CssController.php | 32 +++++++++++++++++++++++++++-- core/Controller/JsController.php | 32 +++++++++++++++++++++++++++-- lib/private/Template/JSCombiner.php | 7 +++++++ lib/private/Template/SCSSCacher.php | 11 +++++++++- 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/core/Controller/CssController.php b/core/Controller/CssController.php index 1206c95a5b833..b467d386f98c0 100644 --- a/core/Controller/CssController.php +++ b/core/Controller/CssController.php @@ -28,6 +28,8 @@ use OCP\AppFramework\Utility\ITimeFactory; use OCP\Files\IAppData; use OCP\Files\NotFoundException; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\SimpleFS\ISimpleFolder; use OCP\IRequest; class CssController extends Controller { @@ -62,12 +64,16 @@ public function __construct($appName, IRequest $request, IAppData $appData, ITim public function getCss($fileName, $appName) { try { $folder = $this->appData->getFolder($appName); - $cssFile = $folder->getFile($fileName); + $gzip = false; + $file = $this->getFile($folder, $fileName, $gzip); } catch(NotFoundException $e) { return new NotFoundResponse(); } - $response = new FileDisplayResponse($cssFile, Http::STATUS_OK, ['Content-Type' => 'text/css']); + $response = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => 'text/css']); + if ($gzip) { + $response->addHeader('Content-Encoding', 'gzip'); + } $response->cacheFor(86400); $expires = new \DateTime(); $expires->setTimestamp($this->timeFactory->getTime()); @@ -76,4 +82,26 @@ public function getCss($fileName, $appName) { $response->addHeader('Pragma', 'cache'); return $response; } + + /** + * @param ISimpleFolder $folder + * @param string $fileName + * @param bool $gzip is set to true if we use the gzip file + * @return ISimpleFile + */ + private function getFile(ISimpleFolder $folder, $fileName, &$gzip) { + $encoding = $this->request->getHeader('Accept-Encoding'); + + if ($encoding !== null && strpos($encoding, 'gzip') !== false) { + try { + $gzip = true; + return $folder->getFile($fileName . '.gz'); + } catch (NotFoundException $e) { + // continue + } + } + + $gzip = false; + return $folder->getFile($fileName); + } } diff --git a/core/Controller/JsController.php b/core/Controller/JsController.php index 0770974e7a152..0b50abc158ad3 100644 --- a/core/Controller/JsController.php +++ b/core/Controller/JsController.php @@ -29,6 +29,8 @@ use OCP\AppFramework\Utility\ITimeFactory; use OCP\Files\IAppData; use OCP\Files\NotFoundException; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\SimpleFS\ISimpleFolder; use OCP\IRequest; class JsController extends Controller { @@ -63,12 +65,16 @@ public function __construct($appName, IRequest $request, IAppData $appData, ITim public function getJs($fileName, $appName) { try { $folder = $this->appData->getFolder($appName); - $jsFile = $folder->getFile($fileName); + $gzip = false; + $file = $this->getFile($folder, $fileName, $gzip); } catch(NotFoundException $e) { return new NotFoundResponse(); } - $response = new FileDisplayResponse($jsFile, Http::STATUS_OK, ['Content-Type' => 'application/javascript']); + $response = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => 'application/javascript']); + if ($gzip) { + $response->addHeader('Content-Encoding', 'gzip'); + } $response->cacheFor(86400); $expires = new \DateTime(); $expires->setTimestamp($this->timeFactory->getTime()); @@ -77,4 +83,26 @@ public function getJs($fileName, $appName) { $response->addHeader('Pragma', 'cache'); return $response; } + + /** + * @param ISimpleFolder $folder + * @param string $fileName + * @param bool $gzip is set to true if we use the gzip file + * @return ISimpleFile + */ + private function getFile(ISimpleFolder $folder, $fileName, &$gzip) { + $encoding = $this->request->getHeader('Accept-Encoding'); + + if ($encoding !== null && strpos($encoding, 'gzip') !== false) { + try { + $gzip = true; + return $folder->getFile($fileName . '.gz'); + } catch (NotFoundException $e) { + // continue + } + } + + $gzip = false; + return $folder->getFile($fileName); + } } diff --git a/lib/private/Template/JSCombiner.php b/lib/private/Template/JSCombiner.php index 9f92813f90563..0f30fb915f775 100644 --- a/lib/private/Template/JSCombiner.php +++ b/lib/private/Template/JSCombiner.php @@ -154,9 +154,16 @@ protected function cache($path, $fileName, ISimpleFolder $folder) { $depFile = $folder->newFile($depFileName); } + try { + $gzipFile = $folder->getFile($fileName . '.gz'); + } catch (NotFoundException $e) { + $gzipFile = $folder->newFile($fileName . '.gz'); + } + try { $cachedfile->putContent($res); $depFile->putContent(json_encode($deps)); + $gzipFile->putContent(gzencode($res, 9)); return true; } catch (NotPermittedException $e) { return false; diff --git a/lib/private/Template/SCSSCacher.php b/lib/private/Template/SCSSCacher.php index c12d87715131f..f3c2ec163cc1c 100644 --- a/lib/private/Template/SCSSCacher.php +++ b/lib/private/Template/SCSSCacher.php @@ -186,9 +186,18 @@ private function cache($path, $fileNameCSS, $fileNameSCSS, ISimpleFolder $folder return false; } + // Gzip file try { - $cachedfile->putContent($this->rebaseUrls($compiledScss, $webDir)); + $gzipFile = $folder->getFile($fileNameCSS . '.gz'); + } catch (NotFoundException $e) { + $gzipFile = $folder->newFile($fileNameCSS . '.gz'); + } + + try { + $data = $this->rebaseUrls($compiledScss, $webDir); + $cachedfile->putContent($data); $depFile->putContent(json_encode($scss->getParsedFiles())); + $gzipFile->putContent(gzencode($data), 9); $this->logger->debug($webDir.'/'.$fileNameSCSS.' compiled and successfully cached', ['app' => 'core']); return true; } catch(NotPermittedException $e) { From a40405531cc0a5fcb81dc5d12b739a7f951948e9 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Tue, 28 Mar 2017 23:13:59 +0200 Subject: [PATCH 2/3] Fix tests Signed-off-by: Roeland Jago Douma --- lib/private/Template/SCSSCacher.php | 2 +- tests/lib/Template/JSCombinerTest.php | 83 +++++++++++++++++++++------ tests/lib/Template/SCSSCacherTest.php | 61 ++++++++++++++++---- 3 files changed, 117 insertions(+), 29 deletions(-) diff --git a/lib/private/Template/SCSSCacher.php b/lib/private/Template/SCSSCacher.php index f3c2ec163cc1c..df2e023250886 100644 --- a/lib/private/Template/SCSSCacher.php +++ b/lib/private/Template/SCSSCacher.php @@ -197,7 +197,7 @@ private function cache($path, $fileNameCSS, $fileNameSCSS, ISimpleFolder $folder $data = $this->rebaseUrls($compiledScss, $webDir); $cachedfile->putContent($data); $depFile->putContent(json_encode($scss->getParsedFiles())); - $gzipFile->putContent(gzencode($data), 9); + $gzipFile->putContent(gzencode($data, 9)); $this->logger->debug($webDir.'/'.$fileNameSCSS.' compiled and successfully cached', ['app' => 'core']); return true; } catch(NotPermittedException $e) { diff --git a/tests/lib/Template/JSCombinerTest.php b/tests/lib/Template/JSCombinerTest.php index 7b09b8792250d..1e6234a062c93 100644 --- a/tests/lib/Template/JSCombinerTest.php +++ b/tests/lib/Template/JSCombinerTest.php @@ -22,6 +22,7 @@ */ namespace Test\Template; +use function foo\func; use OC\SystemConfig; use OC\Template\JSCombiner; use OCP\Files\IAppData; @@ -100,17 +101,18 @@ public function testProcessUncachedFileNoAppDataFolder() { $this->appData->expects($this->once())->method('getFolder')->with('awesomeapp')->willThrowException(new NotFoundException()); $this->appData->expects($this->once())->method('newFolder')->with('awesomeapp')->willReturn($folder); $file = $this->createMock(ISimpleFile::class); + $gzfile = $this->createMock(ISimpleFile::class); $fileDeps = $this->createMock(ISimpleFile::class); $folder->method('getFile') - ->will($this->returnCallback(function($path) use ($file) { + ->will($this->returnCallback(function($path) use ($file, $gzfile) { if ($path === 'combine.js') { return $file; - } - - if ($path === 'combine.js.deps') { + } else if ($path === 'combine.js.deps') { throw new NotFoundException(); + } else if ($path === 'combine.js.gz') { + return $gzfile; } $this->fail(); })); @@ -137,17 +139,17 @@ public function testProcessUncachedFile() { $folder = $this->createMock(ISimpleFolder::class); $this->appData->expects($this->once())->method('getFolder')->with('awesomeapp')->willReturn($folder); $file = $this->createMock(ISimpleFile::class); - $fileDeps = $this->createMock(ISimpleFile::class); + $gzfile = $this->createMock(ISimpleFile::class); $folder->method('getFile') - ->will($this->returnCallback(function($path) use ($file) { + ->will($this->returnCallback(function($path) use ($file, $gzfile) { if ($path === 'combine.js') { return $file; - } - - if ($path === 'combine.js.deps') { + } else if ($path === 'combine.js.deps') { throw new NotFoundException(); + } else if ($path === 'combine.js.gz') { + return $gzfile; } $this->fail(); })); @@ -288,16 +290,28 @@ public function testCacheNoFile() { $folder = $this->createMock(ISimpleFolder::class); $file = $this->createMock(ISimpleFile::class); $depsFile = $this->createMock(ISimpleFile::class); + $gzFile = $this->createMock(ISimpleFile::class); $path = __DIR__ . '/data/'; - $folder->expects($this->at(0))->method('getFile')->with($fileName)->willThrowException(new NotFoundException()); - $folder->expects($this->at(1))->method('newFile')->with($fileName)->willReturn($file); - $folder->expects($this->at(2))->method('getFile')->with($fileName . '.deps')->willThrowException(new NotFoundException()); - $folder->expects($this->at(3))->method('newFile')->with($fileName . '.deps')->willReturn($depsFile); + $folder->method('getFile')->willThrowException(new NotFoundException()); + + $folder->method('newFile')->will($this->returnCallback( + function ($filename) use ($file, $depsFile, $gzFile) { + if ($filename === 'combine.js') { + return $file; + } else if ($filename === 'combine.js.deps') { + return $depsFile; + } else if ($filename === 'combine.js.gz') { + return $gzFile; + } + $this->fail(); + } + )); $file->expects($this->once())->method('putContent'); $depsFile->expects($this->once())->method('putContent'); + $gzFile->expects($this->once())->method('putContent'); $actual = self::invokePrivate($this->jsCombiner, 'cache', [$path, 'combine.json', $folder]); $this->assertTrue($actual); @@ -309,14 +323,26 @@ public function testCache() { $folder = $this->createMock(ISimpleFolder::class); $file = $this->createMock(ISimpleFile::class); $depsFile = $this->createMock(ISimpleFile::class); + $gzFile = $this->createMock(ISimpleFile::class); $path = __DIR__ . '/data/'; - $folder->expects($this->at(0))->method('getFile')->with($fileName)->willReturn($file); - $folder->expects($this->at(1))->method('getFile')->with($fileName . '.deps')->willReturn($depsFile); + $folder->method('getFile')->will($this->returnCallback( + function ($filename) use ($file, $depsFile, $gzFile) { + if ($filename === 'combine.js') { + return $file; + } else if ($filename === 'combine.js.deps') { + return $depsFile; + } else if ($filename === 'combine.js.gz') { + return $gzFile; + } + $this->fail(); + } + )); $file->expects($this->once())->method('putContent'); $depsFile->expects($this->once())->method('putContent'); + $gzFile->expects($this->once())->method('putContent'); $actual = self::invokePrivate($this->jsCombiner, 'cache', [$path, 'combine.json', $folder]); $this->assertTrue($actual); @@ -364,11 +390,23 @@ public function testCacheSuccess() { $folder = $this->createMock(ISimpleFolder::class); $file = $this->createMock(ISimpleFile::class); $depsFile = $this->createMock(ISimpleFile::class); + $gzFile = $this->createMock(ISimpleFile::class); $path = __DIR__ . '/data/'; - $folder->expects($this->at(0))->method('getFile')->with($fileName)->willReturn($file); - $folder->expects($this->at(1))->method('getFile')->with($fileName . '.deps')->willReturn($depsFile); + + $folder->method('getFile')->will($this->returnCallback( + function ($filename) use ($file, $depsFile, $gzFile) { + if ($filename === 'combine.js') { + return $file; + } else if ($filename === 'combine.js.deps') { + return $depsFile; + } else if ($filename === 'combine.js.gz') { + return $gzFile; + } + $this->fail(); + } + )); $file->expects($this->at(0)) ->method('putContent') @@ -385,6 +423,17 @@ function ($content) { return array_key_exists(__DIR__ . '/data//1.js', $deps) && array_key_exists(__DIR__ . '/data//2.js', $deps); })); + $gzFile->expects($this->at(0))->method('putContent')->with($this->callback( + function ($content) { + return gzdecode($content) === 'var a = \'hello\'; + + +var b = \'world\'; + + +'; + } + )); $actual = self::invokePrivate($this->jsCombiner, 'cache', [$path, 'combine.json', $folder]); $this->assertTrue($actual); diff --git a/tests/lib/Template/SCSSCacherTest.php b/tests/lib/Template/SCSSCacherTest.php index 898ea89cf4030..887fa1ed605a4 100644 --- a/tests/lib/Template/SCSSCacherTest.php +++ b/tests/lib/Template/SCSSCacherTest.php @@ -73,13 +73,16 @@ public function testProcessUncachedFileNoAppDataFolder() { $file = $this->createMock(ISimpleFile::class); $file->expects($this->any())->method('getSize')->willReturn(1); $fileDeps = $this->createMock(ISimpleFile::class); + $gzfile = $this->createMock(ISimpleFile::class); $folder->method('getFile') - ->will($this->returnCallback(function($path) use ($file) { + ->will($this->returnCallback(function($path) use ($file, $gzfile) { if ($path === 'styles.css') { return $file; } else if ($path === 'styles.css.deps') { throw new NotFoundException(); + } else if ($path === 'styles.css.gz') { + return $gzfile; } else { $this->fail(); } @@ -99,14 +102,17 @@ public function testProcessUncachedFile() { $file = $this->createMock(ISimpleFile::class); $file->expects($this->any())->method('getSize')->willReturn(1); $fileDeps = $this->createMock(ISimpleFile::class); + $gzfile = $this->createMock(ISimpleFile::class); $folder->method('getFile') - ->will($this->returnCallback(function($path) use ($file) { + ->will($this->returnCallback(function($path) use ($file, $gzfile) { if ($path === 'styles.css') { return $file; } else if ($path === 'styles.css.deps') { throw new NotFoundException(); - } else { + } else if ($path === 'styles.css.gz') { + return $gzfile; + }else { $this->fail(); } })); @@ -211,17 +217,26 @@ public function testCacheNoFile() { $folder = $this->createMock(ISimpleFolder::class); $file = $this->createMock(ISimpleFile::class); $depsFile = $this->createMock(ISimpleFile::class); + $gzipFile = $this->createMock(ISimpleFile::class); $webDir = "core/css"; $path = \OC::$SERVERROOT . '/core/css/'; - $folder->expects($this->at(0))->method('getFile')->with($fileNameCSS)->willThrowException(new NotFoundException()); - $folder->expects($this->at(1))->method('newFile')->with($fileNameCSS)->willReturn($file); - $folder->expects($this->at(2))->method('getFile')->with($fileNameCSS . '.deps')->willThrowException(new NotFoundException()); - $folder->expects($this->at(3))->method('newFile')->with($fileNameCSS . '.deps')->willReturn($depsFile); + $folder->method('getFile')->willThrowException(new NotFoundException()); + $folder->method('newFile')->will($this->returnCallback(function($fileName) use ($file, $depsFile, $gzipFile) { + if ($fileName === 'styles.css') { + return $file; + } else if ($fileName === 'styles.css.deps') { + return $depsFile; + } else if ($fileName === 'styles.css.gz') { + return $gzipFile; + } + throw new \Exception(); + })); $file->expects($this->once())->method('putContent'); $depsFile->expects($this->once())->method('putContent'); + $gzipFile->expects($this->once())->method('putContent'); $actual = self::invokePrivate($this->scssCacher, 'cache', [$path, $fileNameCSS, $fileNameSCSS, $folder, $webDir]); $this->assertTrue($actual); @@ -233,15 +248,25 @@ public function testCache() { $folder = $this->createMock(ISimpleFolder::class); $file = $this->createMock(ISimpleFile::class); $depsFile = $this->createMock(ISimpleFile::class); + $gzipFile = $this->createMock(ISimpleFile::class); $webDir = "core/css"; $path = \OC::$SERVERROOT; - $folder->expects($this->at(0))->method('getFile')->with($fileNameCSS)->willReturn($file); - $folder->expects($this->at(1))->method('getFile')->with($fileNameCSS . '.deps')->willReturn($depsFile); + $folder->method('getFile')->will($this->returnCallback(function($fileName) use ($file, $depsFile, $gzipFile) { + if ($fileName === 'styles.css') { + return $file; + } else if ($fileName === 'styles.css.deps') { + return $depsFile; + } else if ($fileName === 'styles.css.gz') { + return $gzipFile; + } + throw new \Exception(); + })); $file->expects($this->once())->method('putContent'); $depsFile->expects($this->once())->method('putContent'); + $gzipFile->expects($this->once())->method('putContent'); $actual = self::invokePrivate($this->scssCacher, 'cache', [$path, $fileNameCSS, $fileNameSCSS, $folder, $webDir]); $this->assertTrue($actual); @@ -253,12 +278,21 @@ public function testCacheSuccess() { $folder = $this->createMock(ISimpleFolder::class); $file = $this->createMock(ISimpleFile::class); $depsFile = $this->createMock(ISimpleFile::class); + $gzipFile = $this->createMock(ISimpleFile::class); $webDir = "tests/data/scss"; $path = \OC::$SERVERROOT . $webDir; - $folder->expects($this->at(0))->method('getFile')->with($fileNameCSS)->willReturn($file); - $folder->expects($this->at(1))->method('getFile')->with($fileNameCSS . '.deps')->willReturn($depsFile); + $folder->method('getFile')->will($this->returnCallback(function($fileName) use ($file, $depsFile, $gzipFile) { + if ($fileName === 'styles-success.css') { + return $file; + } else if ($fileName === 'styles-success.css.deps') { + return $depsFile; + } else if ($fileName === 'styles-success.css.gz') { + return $gzipFile; + } + throw new \Exception(); + })); $file->expects($this->at(0))->method('putContent')->with($this->callback( function ($content){ @@ -270,6 +304,11 @@ function ($content) { return array_key_exists(\OC::$SERVERROOT . '/core/css/variables.scss', $deps) && array_key_exists(\OC::$SERVERROOT . '/tests/data/scss/styles-success.scss', $deps); })); + $gzipFile->expects($this->at(0))->method('putContent')->with($this->callback( + function ($content) { + return gzdecode($content) === 'body{background-color:#0082c9}'; + } + )); $actual = self::invokePrivate($this->scssCacher, 'cache', [$path, $fileNameCSS, $fileNameSCSS, $folder, $webDir]); $this->assertTrue($actual); From 3a0ef65f332691177ee8449fce54296c80c69f1f Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Tue, 28 Mar 2017 23:42:20 +0200 Subject: [PATCH 3/3] Fix controller tests Signed-off-by: Roeland Jago Douma --- tests/Core/Controller/CssControllerTest.php | 68 ++++++++++++++++++++- tests/Core/Controller/JsControllerTest.php | 68 ++++++++++++++++++++- 2 files changed, 134 insertions(+), 2 deletions(-) diff --git a/tests/Core/Controller/CssControllerTest.php b/tests/Core/Controller/CssControllerTest.php index 60fef9dddade5..7fa358e056efa 100644 --- a/tests/Core/Controller/CssControllerTest.php +++ b/tests/Core/Controller/CssControllerTest.php @@ -40,6 +40,9 @@ class CssControllerTest extends TestCase { /** @var IAppData|\PHPUnit_Framework_MockObject_MockObject */ private $appData; + /** @var IRequests|\PHPUnit_Framework_MockObject_MockObject */ + private $request; + /** @var CssController */ private $controller; @@ -52,9 +55,11 @@ public function setUp() { $timeFactory->method('getTime') ->willReturn(1337); + $this->request = $this->createMock(IRequest::class); + $this->controller = new CssController( 'core', - $this->createMock(IRequest::class), + $this->request, $this->appData, $timeFactory ); @@ -108,4 +113,65 @@ public function testGetFile() { $this->assertEquals($expected, $result); } + public function testGetGzipFile() { + $folder = $this->createMock(ISimpleFolder::class); + $gzipFile = $this->createMock(ISimpleFile::class); + $this->appData->method('getFolder') + ->with('myapp') + ->willReturn($folder); + + $folder->method('getFile') + ->with('file.css.gz') + ->willReturn($gzipFile); + + $this->request->method('getHeader') + ->with('Accept-Encoding') + ->willReturn('gzip, deflate'); + + $expected = new FileDisplayResponse($gzipFile, Http::STATUS_OK, ['Content-Type' => 'text/css']); + $expected->addHeader('Content-Encoding', 'gzip'); + $expected->cacheFor(86400); + $expires = new \DateTime(); + $expires->setTimestamp(1337); + $expires->add(new \DateInterval('PT24H')); + $expected->addHeader('Expires', $expires->format(\DateTime::RFC1123)); + $expected->addHeader('Pragma', 'cache'); + + $result = $this->controller->getCss('file.css', 'myapp'); + $this->assertEquals($expected, $result); + } + + public function testGetGzipFileNotFound() { + $folder = $this->createMock(ISimpleFolder::class); + $file = $this->createMock(ISimpleFile::class); + $this->appData->method('getFolder') + ->with('myapp') + ->willReturn($folder); + + $folder->method('getFile') + ->will($this->returnCallback( + function($fileName) use ($file) { + if ($fileName === 'file.css') { + return $file; + } + throw new NotFoundException(); + }) + ); + + $this->request->method('getHeader') + ->with('Accept-Encoding') + ->willReturn('gzip, deflate'); + + $expected = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => 'text/css']); + $expected->cacheFor(86400); + $expires = new \DateTime(); + $expires->setTimestamp(1337); + $expires->add(new \DateInterval('PT24H')); + $expected->addHeader('Expires', $expires->format(\DateTime::RFC1123)); + $expected->addHeader('Pragma', 'cache'); + + $result = $this->controller->getCss('file.css', 'myapp'); + $this->assertEquals($expected, $result); + } + } diff --git a/tests/Core/Controller/JsControllerTest.php b/tests/Core/Controller/JsControllerTest.php index febb785f60d82..8f48a7c339059 100644 --- a/tests/Core/Controller/JsControllerTest.php +++ b/tests/Core/Controller/JsControllerTest.php @@ -42,6 +42,9 @@ class JsControllerTest extends TestCase { /** @var JsController */ private $controller; + /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */ + private $request; + public function setUp() { parent::setUp(); @@ -51,9 +54,11 @@ public function setUp() { $timeFactory->method('getTime') ->willReturn(1337); + $this->request = $this->createMock(IRequest::class); + $this->controller = new JsController( 'core', - $this->createMock(IRequest::class), + $this->request, $this->appData, $timeFactory ); @@ -107,4 +112,65 @@ public function testGetFile() { $this->assertEquals($expected, $result); } + public function testGetGzipFile() { + $folder = $this->createMock(ISimpleFolder::class); + $gzipFile = $this->createMock(ISimpleFile::class); + $this->appData->method('getFolder') + ->with('myapp') + ->willReturn($folder); + + $folder->method('getFile') + ->with('file.js.gz') + ->willReturn($gzipFile); + + $this->request->method('getHeader') + ->with('Accept-Encoding') + ->willReturn('gzip, deflate'); + + $expected = new FileDisplayResponse($gzipFile, Http::STATUS_OK, ['Content-Type' => 'application/javascript']); + $expected->addHeader('Content-Encoding', 'gzip'); + $expected->cacheFor(86400); + $expires = new \DateTime(); + $expires->setTimestamp(1337); + $expires->add(new \DateInterval('PT24H')); + $expected->addHeader('Expires', $expires->format(\DateTime::RFC1123)); + $expected->addHeader('Pragma', 'cache'); + + $result = $this->controller->getJs('file.js', 'myapp'); + $this->assertEquals($expected, $result); + } + + public function testGetGzipFileNotFound() { + $folder = $this->createMock(ISimpleFolder::class); + $file = $this->createMock(ISimpleFile::class); + $this->appData->method('getFolder') + ->with('myapp') + ->willReturn($folder); + + $folder->method('getFile') + ->will($this->returnCallback( + function($fileName) use ($file) { + if ($fileName === 'file.js') { + return $file; + } + throw new NotFoundException(); + }) + ); + + $this->request->method('getHeader') + ->with('Accept-Encoding') + ->willReturn('gzip, deflate'); + + $expected = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => 'application/javascript']); + $expected->cacheFor(86400); + $expires = new \DateTime(); + $expires->setTimestamp(1337); + $expires->add(new \DateInterval('PT24H')); + $expected->addHeader('Expires', $expires->format(\DateTime::RFC1123)); + $expected->addHeader('Pragma', 'cache'); + + $result = $this->controller->getJs('file.js', 'myapp'); + $this->assertEquals($expected, $result); + } + }