From f056663159008531655298a6fa60b36e2d061530 Mon Sep 17 00:00:00 2001 From: Chris Brown Date: Fri, 21 Feb 2020 17:39:51 -0500 Subject: [PATCH 1/5] Add proxy site-handling commands valet proxy domain host valet unproxy domain valet proxies (to list all recognized proxy site configs) --- cli/Valet/Site.php | 168 +++++++++++++++++++++++++++++++++++-- cli/stubs/proxy.valet.conf | 95 +++++++++++++++++++++ cli/valet.php | 29 +++++++ 3 files changed, 285 insertions(+), 7 deletions(-) create mode 100644 cli/stubs/proxy.valet.conf diff --git a/cli/Valet/Site.php b/cli/Valet/Site.php index 6827dd5b1..61f367c3c 100644 --- a/cli/Valet/Site.php +++ b/cli/Valet/Site.php @@ -145,6 +145,75 @@ function parked() return $parkedLinks; } + /** + * Get all sites which are proxies (not Links, and contain proxy_pass directive) + * + * @return \Illuminate\Support\Collection + */ + function proxies() + { + $dir = VALET_HOME_PATH.'/Nginx/'; + $tld = $this->config->read()['tld']; + $links = $this->links(); + $certsPath = VALET_HOME_PATH.'/Certificates'; + $certs = $this->getCertificates($certsPath); + + $proxies = collect($this->files->scandir($dir)) + ->filter(function ($site, $key) use ($tld) { + // keep sites that match our TLD + return ends_with($site, '.'.$tld); + })->map(function ($site, $key) use ($tld) { + // remove the TLD suffix for consistency + return str_replace('.'.$tld, '', $site); + })->reject(function ($site, $key) use ($links) { + return $links->has($site); + })->mapWithKeys(function ($site) { + $host = $this->getProxyHostForSite($site) ?: '(other)'; + return [$site => $host]; + })->reject(function ($host, $site) { + // If proxy host is null, it may be just a normal SSL stub, or something else; either way we exclude it from the list + return $host === '(other)'; + })->map(function ($host, $site) use ($certs) { + $secured = $certs->has($site); + $url = ($secured ? 'https': 'http').'://'.$site; + + return [ + 'site' => $site, + 'secured' => $secured ? ' X': '', + 'url' => $url, + 'path' => $host, + ]; + }); + + return $proxies; + } + + /** + * Identify whether a site is for a proxy by reading the host name from its config file. + * + * @param string $site Site name without TLD + * @param string $configContents Config file contents + * @return string|null + */ + function getProxyHostForSite($site, $configContents = null) + { + $siteConf = $configContents ?: $this->getSiteConfigFileContents($site); + $host = null; + if (preg_match('~proxy_pass\s+(?https?://.*)\s*;~', $siteConf, $patterns)) { + $host = trim($patterns['host']); + } + return $host; + } + + function getSiteConfigFileContents($site, $dir = null) + { + $config = $this->config->read(); + $dir = $dir ?: VALET_HOME_PATH.'/Nginx/'; + $suffix = '.'.$config['tld']; + $file = $dir.str_replace($suffix,'',$site).$suffix; + return $this->files->get($file); + } + /** * Get all certificates from config folder. * @@ -265,12 +334,41 @@ function resecureForNewTld($oldTld, $tld) $secured = $this->secured(); foreach ($secured as $url) { - $this->unsecure($url); + $newUrl = str_replace('.'.$oldTld, '.'.$tld, $url); + $siteConf = $this->getSiteConfigFileContents($url); + + if (strpos($siteConf, '# valet stub: proxy.valet.conf') === 0) { + $this->unsecure($url); + $this->secure($newUrl, $this->replaceOldDomainWithNew($siteConf, '.'.$url, '.'.$newUrl)); + } else { + $this->unsecure($url); + $this->secure($newUrl); + } } + } - foreach ($secured as $url) { - $this->secure(str_replace('.'.$oldTld, '.'.$tld, $url)); + /** + * Parse Nginx site config file contents to swap old domain to new. + * + * @param string $siteConf Nginx site config content + * @param string $old Old domain + * @param string $new New domain + * @return string + */ + function replaceOldDomainWithNew($siteConf, $old, $new) + { + $lookups = []; + $lookups[] = '~server_name .*;~'; + $lookups[] = '~error_log .*;~'; + + foreach ($lookups as $lookup) { + preg_match($lookup, $siteConf, $matches); + foreach ($matches as $match) { + $replaced = str_replace($old, $new, $match); + $siteConf = str_replace($match, $replaced, $siteConf); + } } + return $siteConf; } /** @@ -290,9 +388,10 @@ function secured() * Secure the given host with TLS. * * @param string $url + * @param string $siteConf pregenerated Nginx config file contents * @return void */ - function secure($url) + function secure($url, $siteConf = null) { $this->unsecure($url); @@ -305,7 +404,7 @@ function secure($url) $this->createCertificate($url); $this->files->putAsUser( - VALET_HOME_PATH.'/Nginx/'.$url, $this->buildSecureNginxServer($url) + VALET_HOME_PATH.'/Nginx/'.$url, $this->buildSecureNginxServer($url, $siteConf) ); } @@ -453,16 +552,21 @@ function buildCertificateConf($path, $url) * Build the TLS secured Nginx server for the given URL. * * @param string $url + * @param string $siteConf (optional) Nginx site config file content * @return string */ - function buildSecureNginxServer($url) + function buildSecureNginxServer($url, $siteConf = null) { $path = $this->certificatesPath(); + if ($siteConf === null) { + $siteConf = $this->files->get(__DIR__.'/../stubs/secure.valet.conf'); + } + return str_replace( ['VALET_HOME_PATH', 'VALET_SERVER_PATH', 'VALET_STATIC_PREFIX', 'VALET_SITE', 'VALET_CERT', 'VALET_KEY'], [VALET_HOME_PATH, VALET_SERVER_PATH, VALET_STATIC_PREFIX, $url, $path.'/'.$url.'.crt', $path.'/'.$url.'.key'], - $this->files->get(__DIR__.'/../stubs/secure.valet.conf') + $siteConf ); } @@ -522,6 +626,56 @@ function unsecureAll() info('unsecure --all was successful.'); } + /** + * Build the Nginx proxy config for the specified domain + * + * @param string $url The domain name to serve + * @param string $host The URL to proxy to, eg: http://127.0.0.1:8080 + * @return string + */ + function proxyCreate($url, $host) + { + if (!preg_match('~^https?://.*$~', $host)) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid URL', $host)); + } + + $tld = $this->config->read()['tld']; + if (!ends_with($url, '.'.$tld)) { + $url .= '.'.$tld; + } + + $siteConf = $this->files->get(__DIR__.'/../stubs/proxy.valet.conf'); + + $siteConf = str_replace( + ['VALET_HOME_PATH', 'VALET_SERVER_PATH', 'VALET_STATIC_PREFIX', 'VALET_SITE', 'VALET_PROXY_HOST'], + [VALET_HOME_PATH, VALET_SERVER_PATH, VALET_STATIC_PREFIX, $url, $host], + $siteConf + ); + + $this->secure($url, $siteConf); + + info('Valet will now proxy [https://'.$url.'] traffic to ['.$host.'].'); + } + + /** + * Unsecure the given URL so that it will use HTTP again. + * + * @param string $url + * @return void + */ + function proxyDelete($url) + { + $tld = $this->config->read()['tld']; + if (!ends_with($url, '.'.$tld)) { + $url .= '.'.$tld; + } + + $this->unsecure($url); + $this->files->unlink(VALET_HOME_PATH.'/Nginx/'.$url); + + info('Valet will no longer proxy [https://'.$url.'].'); + } + /** * Get the path to the linked Valet sites. * diff --git a/cli/stubs/proxy.valet.conf b/cli/stubs/proxy.valet.conf new file mode 100644 index 000000000..a82d95dd0 --- /dev/null +++ b/cli/stubs/proxy.valet.conf @@ -0,0 +1,95 @@ +# valet stub: proxy.valet.conf + +server { + listen 127.0.0.1:80; + server_name VALET_SITE www.VALET_SITE *.VALET_SITE; + return 301 https://$host$request_uri; +} + +server { + listen 127.0.0.1:443 ssl http2; + server_name VALET_SITE www.VALET_SITE *.VALET_SITE; + root /; + charset utf-8; + client_max_body_size 128M; + http2_push_preload on; + + location /VALET_STATIC_PREFIX/ { + internal; + alias /; + try_files $uri $uri/; + } + + ssl_certificate "VALET_CERT"; + ssl_certificate_key "VALET_KEY"; + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + access_log off; + error_log "VALET_HOME_PATH/Log/VALET_SITE-error.log"; + + error_page 404 "VALET_SERVER_PATH"; + + location / { + proxy_pass VALET_PROXY_HOST; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Client-Verify SUCCESS; + proxy_set_header X-Client-DN $ssl_client_s_dn; + proxy_set_header X-SSL-Subject $ssl_client_s_dn; + proxy_set_header X-SSL-Issuer $ssl_client_i_dn; + proxy_set_header X-NginX-Proxy true; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_http_version 1.1; + proxy_read_timeout 1800; + proxy_connect_timeout 1800; + chunked_transfer_encoding on; + proxy_redirect off; + proxy_buffering off; + } + + location ~ /\.ht { + deny all; + } +} + +server { + listen 127.0.0.1:60; + server_name VALET_SITE www.VALET_SITE *.VALET_SITE; + root /; + charset utf-8; + client_max_body_size 128M; + + add_header X-Robots-Tag 'noindex, nofollow, nosnippet, noarchive'; + + location /VALET_STATIC_PREFIX/ { + internal; + alias /; + try_files $uri $uri/; + } + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + access_log off; + error_log "VALET_HOME_PATH/Log/VALET_SITE-error.log"; + + error_page 404 "VALET_SERVER_PATH"; + + location / { + proxy_pass VALET_PROXY_HOST; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location ~ /\.ht { + deny all; + } +} + diff --git a/cli/valet.php b/cli/valet.php index 45726fcde..b13b5ebc4 100755 --- a/cli/valet.php +++ b/cli/valet.php @@ -181,6 +181,35 @@ info('The ['.$url.'] site will now serve traffic over HTTP.'); })->descriptions('Stop serving the given domain over HTTPS and remove the trusted TLS certificate'); + /** + * Create an Nginx proxy config for the specified domain + */ + $app->command('proxy domain host', function ($domain, $host) { + + Site::proxyCreate($domain, $host); + Nginx::restart(); + + })->descriptions('Create an Nginx proxy site for the specified host. Useful for docker, mailhog etc.'); + + /** + * Delete an Nginx proxy config + */ + $app->command('unproxy domain', function ($domain) { + + Site::proxyDelete($domain); + Nginx::restart(); + + })->descriptions('Delete an Nginx proxy config.'); + + /** + * Display all of the sites that are proxies. + */ + $app->command('proxies', function () { + $proxies = Site::proxies(); + + table(['Site', 'SSL', 'URL', 'Host'], $proxies->all()); + })->descriptions('Display all of the proxy sites'); + /** * Determine which Valet driver the current directory is using. */ From 9f607ba99865de5638b68004d9a53e366757c708 Mon Sep 17 00:00:00 2001 From: Beau Simensen Date: Tue, 28 Apr 2020 11:37:20 -0500 Subject: [PATCH 2/5] Test the new Proxies features Updated the `proxies` method to return the URL + .tld as the rendered output wasn't exactly what one would expect. While not critical, it was not consistent. Refactored some of the Site class to aid in testing using a fake instead of relying on a ton of mocking. The Site fake has support for both using `tests/output` as well as named fixtures (`tests/fixtures/Proxies`). Testing for certificate I/O is pretty whack, but this is the best I could come up with that would still ensure the certificate stuff was getting called without actually requiring `sudo` to run phpunit. Replaced instances referring directly to `VALET_HOME_PATH` with calls to the new `valetHomePath()`. This method is taken over in the fake Site implementation so that everything runs the same using the fake Site without having to change other assumptions. Updated several "path" methods (and many of their usages) to take the "thing" you are looking for so you can either get the path to the type of "thing" you are looking for or the direct path to the specific "thing" you are looking for. Examples: ``` // ~/.config/valet/Nginx $site->nginxPath(); // ~/.config/valet/Nginx/some-site.com.test $site->nginxPath('some-site.com.test'); ``` Made some other tests related to the existence of `Sites` directory that resulted in updating the mocks for many of the other tests. All in all, it should make other aspects of the Site class handle things more gracefully if `Sites` doesn't exist. --- cli/Valet/Site.php | 119 ++++--- tests/SiteTest.php | 319 +++++++++++++++++- .../Certificates/some-proxy.com.test.crt | 0 .../Proxies/Nginx/not-a-proxy.com.test | 90 +++++ .../Proxies/Nginx/some-other-proxy.com.test | 95 ++++++ .../Proxies/Nginx/some-proxy.com.test | 95 ++++++ 6 files changed, 670 insertions(+), 48 deletions(-) create mode 100644 tests/fixtures/Proxies/Certificates/some-proxy.com.test.crt create mode 100644 tests/fixtures/Proxies/Nginx/not-a-proxy.com.test create mode 100644 tests/fixtures/Proxies/Nginx/some-other-proxy.com.test create mode 100644 tests/fixtures/Proxies/Nginx/some-proxy.com.test diff --git a/cli/Valet/Site.php b/cli/Valet/Site.php index 61f367c3c..d497b93b7 100644 --- a/cli/Valet/Site.php +++ b/cli/Valet/Site.php @@ -68,7 +68,7 @@ private function getLinkNameByCurrentDir() function host($path) { foreach ($this->files->scandir($this->sitesPath()) as $link) { - if ($resolved = realpath($this->sitesPath().'/'.$link) === $path) { + if ($resolved = realpath($this->sitesPath($link)) === $path) { return $link; } } @@ -103,7 +103,7 @@ function link($target, $link) */ function links() { - $certsPath = VALET_HOME_PATH.'/Certificates'; + $certsPath = $this->certificatesPath(); $this->files->ensureDirExists($certsPath, user()); @@ -119,11 +119,7 @@ function links() */ function parked() { - $certsPath = VALET_HOME_PATH.'/Certificates'; - - $this->files->ensureDirExists($certsPath, user()); - - $certs = $this->getCertificates($certsPath); + $certs = $this->getCertificates(); $links = $this->getSites($this->sitesPath(), $certs); @@ -152,11 +148,14 @@ function parked() */ function proxies() { - $dir = VALET_HOME_PATH.'/Nginx/'; + $dir = $this->nginxPath(); $tld = $this->config->read()['tld']; $links = $this->links(); - $certsPath = VALET_HOME_PATH.'/Certificates'; - $certs = $this->getCertificates($certsPath); + $certs = $this->getCertificates(); + + if (! $this->files->exists($dir)) { + return collect(); + } $proxies = collect($this->files->scandir($dir)) ->filter(function ($site, $key) use ($tld) { @@ -173,9 +172,9 @@ function proxies() })->reject(function ($host, $site) { // If proxy host is null, it may be just a normal SSL stub, or something else; either way we exclude it from the list return $host === '(other)'; - })->map(function ($host, $site) use ($certs) { + })->map(function ($host, $site) use ($certs, $tld) { $secured = $certs->has($site); - $url = ($secured ? 'https': 'http').'://'.$site; + $url = ($secured ? 'https': 'http').'://'.$site.'.'.$tld; return [ 'site' => $site, @@ -205,13 +204,12 @@ function getProxyHostForSite($site, $configContents = null) return $host; } - function getSiteConfigFileContents($site, $dir = null) + function getSiteConfigFileContents($site) { $config = $this->config->read(); - $dir = $dir ?: VALET_HOME_PATH.'/Nginx/'; $suffix = '.'.$config['tld']; - $file = $dir.str_replace($suffix,'',$site).$suffix; - return $this->files->get($file); + $file = str_replace($suffix,'',$site).$suffix; + return $this->files->get($this->nginxPath($file)); } /** @@ -220,8 +218,12 @@ function getSiteConfigFileContents($site, $dir = null) * @param string $path * @return \Illuminate\Support\Collection */ - function getCertificates($path) + function getCertificates($path = null) { + $path = is_null($path) ? $this->certificatesPath() : $path; + + $this->files->ensureDirExists($path, user()); + $config = $this->config->read(); return collect($this->files->scandir($path))->filter(function ($value, $key) { @@ -265,6 +267,8 @@ function getSites($path, $certs) { $config = $this->config->read(); + $this->files->ensureDirExists($path, user()); + return collect($this->files->scandir($path))->mapWithKeys(function ($site) use ($path) { $sitePath = $path.'/'.$site; @@ -299,7 +303,7 @@ function unlink($name) { $name = $this->getRealSiteName($name); - if ($this->files->exists($path = $this->sitesPath().'/'.$name)) { + if ($this->files->exists($path = $this->sitesPath($name))) { $this->files->unlink($path); } @@ -349,7 +353,7 @@ function resecureForNewTld($oldTld, $tld) /** * Parse Nginx site config file contents to swap old domain to new. - * + * * @param string $siteConf Nginx site config content * @param string $old Old domain * @param string $new New domain @@ -399,12 +403,14 @@ function secure($url, $siteConf = null) $this->files->ensureDirExists($this->certificatesPath(), user()); + $this->files->ensureDirExists($this->nginxPath(), user()); + $this->createCa(); $this->createCertificate($url); $this->files->putAsUser( - VALET_HOME_PATH.'/Nginx/'.$url, $this->buildSecureNginxServer($url, $siteConf) + $this->nginxPath($url), $this->buildSecureNginxServer($url, $siteConf) ); } @@ -415,8 +421,8 @@ function secure($url, $siteConf = null) */ function createCa() { - $caPemPath = $this->caPath().'/LaravelValetCASelfSigned.pem'; - $caKeyPath = $this->caPath().'/LaravelValetCASelfSigned.key'; + $caPemPath = $this->caPath('LaravelValetCASelfSigned.pem'); + $caKeyPath = $this->caPath('LaravelValetCASelfSigned.key'); if ($this->files->exists($caKeyPath) && $this->files->exists($caPemPath)) { return; @@ -452,13 +458,13 @@ function createCa() */ function createCertificate($url) { - $caPemPath = $this->caPath().'/LaravelValetCASelfSigned.pem'; - $caKeyPath = $this->caPath().'/LaravelValetCASelfSigned.key'; - $caSrlPath = $this->caPath().'/LaravelValetCASelfSigned.srl'; - $keyPath = $this->certificatesPath().'/'.$url.'.key'; - $csrPath = $this->certificatesPath().'/'.$url.'.csr'; - $crtPath = $this->certificatesPath().'/'.$url.'.crt'; - $confPath = $this->certificatesPath().'/'.$url.'.conf'; + $caPemPath = $this->caPath('LaravelValetCASelfSigned.pem'); + $caKeyPath = $this->caPath('LaravelValetCASelfSigned.key'); + $caSrlPath = $this->caPath('LaravelValetCASelfSigned.srl'); + $keyPath = $this->certificatesPath($url, 'key'); + $csrPath = $this->certificatesPath($url, 'csr'); + $crtPath = $this->certificatesPath($url, 'crt'); + $confPath = $this->certificatesPath($url, 'conf'); $this->buildCertificateConf($confPath, $url); $this->createPrivateKey($keyPath); @@ -557,15 +563,20 @@ function buildCertificateConf($path, $url) */ function buildSecureNginxServer($url, $siteConf = null) { - $path = $this->certificatesPath(); - if ($siteConf === null) { $siteConf = $this->files->get(__DIR__.'/../stubs/secure.valet.conf'); } return str_replace( ['VALET_HOME_PATH', 'VALET_SERVER_PATH', 'VALET_STATIC_PREFIX', 'VALET_SITE', 'VALET_CERT', 'VALET_KEY'], - [VALET_HOME_PATH, VALET_SERVER_PATH, VALET_STATIC_PREFIX, $url, $path.'/'.$url.'.crt', $path.'/'.$url.'.key'], + [ + $this->valetHomePath(), + VALET_SERVER_PATH, + VALET_STATIC_PREFIX, + $url, + $this->certificatesPath($url, 'crt'), + $this->certificatesPath($url, 'key'), + ], $siteConf ); } @@ -578,13 +589,13 @@ function buildSecureNginxServer($url, $siteConf = null) */ function unsecure($url) { - if ($this->files->exists($this->certificatesPath().'/'.$url.'.crt')) { - $this->files->unlink(VALET_HOME_PATH.'/Nginx/'.$url); + if ($this->files->exists($this->certificatesPath($url, 'crt'))) { + $this->files->unlink($this->nginxPath($url)); - $this->files->unlink($this->certificatesPath().'/'.$url.'.conf'); - $this->files->unlink($this->certificatesPath().'/'.$url.'.key'); - $this->files->unlink($this->certificatesPath().'/'.$url.'.csr'); - $this->files->unlink($this->certificatesPath().'/'.$url.'.crt'); + $this->files->unlink($this->certificatesPath($url, 'conf')); + $this->files->unlink($this->certificatesPath($url, 'key')); + $this->files->unlink($this->certificatesPath($url, 'csr')); + $this->files->unlink($this->certificatesPath($url, 'crt')); } $this->cli->run(sprintf('sudo security delete-certificate -c "%s" /Library/Keychains/System.keychain', $url)); @@ -648,7 +659,7 @@ function proxyCreate($url, $host) $siteConf = str_replace( ['VALET_HOME_PATH', 'VALET_SERVER_PATH', 'VALET_STATIC_PREFIX', 'VALET_SITE', 'VALET_PROXY_HOST'], - [VALET_HOME_PATH, VALET_SERVER_PATH, VALET_STATIC_PREFIX, $url, $host], + [$this->valetHomePath(), VALET_SERVER_PATH, VALET_STATIC_PREFIX, $url, $host], $siteConf ); @@ -671,19 +682,32 @@ function proxyDelete($url) } $this->unsecure($url); - $this->files->unlink(VALET_HOME_PATH.'/Nginx/'.$url); + $this->files->unlink($this->nginxPath($url)); info('Valet will no longer proxy [https://'.$url.'].'); } + function valetHomePath() + { + return VALET_HOME_PATH; + } + + /** + * Get the path to Nginx site configuration files. + */ + function nginxPath($additionalPath = null) + { + return $this->valetHomePath().'/Nginx'.($additionalPath ? '/'.$additionalPath : ''); + } + /** * Get the path to the linked Valet sites. * * @return string */ - function sitesPath() + function sitesPath($link = null) { - return VALET_HOME_PATH.'/Sites'; + return $this->valetHomePath().'/Sites'.($link ? '/'.$link : ''); } /** @@ -691,9 +715,9 @@ function sitesPath() * * @return string */ - function caPath() + function caPath($caFile = null) { - return VALET_HOME_PATH.'/CA'; + return $this->valetHomePath().'/CA'.($caFile ? '/'.$caFile : ''); } /** @@ -701,8 +725,11 @@ function caPath() * * @return string */ - function certificatesPath() + function certificatesPath($url = null, $extension = null) { - return VALET_HOME_PATH.'/Certificates'; + $url = $url ? '/'.$url : ''; + $extension = $extension ? '.'.$extension : ''; + + return $this->valetHomePath().'/Certificates'.$url.$extension; } } diff --git a/tests/SiteTest.php b/tests/SiteTest.php index c8ba80e5b..405f32096 100644 --- a/tests/SiteTest.php +++ b/tests/SiteTest.php @@ -1,5 +1,6 @@ once() ->with($certPath = '/Users/testuser/.config/valet/Certificates') ->andReturn(['helloworld.multi.segment.tld.com.crt']); + $files->shouldReceive('ensureDirExists') + ->once() + ->with('/Users/testuser/.config/valet/Certificates', user()); $config = Mockery::mock(Configuration::class); $config->shouldReceive('read') ->once() @@ -64,6 +68,9 @@ public function test_get_sites_will_return_if_secured() ->twice() ->andReturn($dirPath . '/sitetwo', $dirPath . '/sitethree'); $files->shouldReceive('isDir')->andReturn(true); + $files->shouldReceive('ensureDirExists') + ->once() + ->with($dirPath, user()); $config = Mockery::mock(Configuration::class); $config->shouldReceive('read') @@ -119,6 +126,9 @@ public function test_get_sites_will_work_with_non_symlinked_path() ->with($dirPath . '/sitetwo') ->andReturn($dirPath . '/sitetwo'); $files->shouldReceive('isDir')->once()->with($dirPath . '/sitetwo')->andReturn(true); + $files->shouldReceive('ensureDirExists') + ->once() + ->with($dirPath, user()); $config = Mockery::mock(Configuration::class); $config->shouldReceive('read') @@ -154,6 +164,9 @@ public function test_get_sites_will_not_return_if_path_is_not_directory() $files->shouldReceive('realpath')->andReturn($dirPath . '/sitetwo', $dirPath . '/siteone'); $files->shouldReceive('isDir')->twice() ->andReturn(false, true); + $files->shouldReceive('ensureDirExists') + ->once() + ->with($dirPath, user()); $config = Mockery::mock(Configuration::class); $config->shouldReceive('read') @@ -194,6 +207,9 @@ public function test_get_sites_will_work_with_symlinked_path() ->with($dirPath . '/siteone') ->andReturn($linkedPath = '/Users/usertest/linkedpath/siteone'); $files->shouldReceive('isDir')->andReturn(true); + $files->shouldReceive('ensureDirExists') + ->once() + ->with($dirPath, user()); $config = Mockery::mock(Configuration::class); $config->shouldReceive('read') @@ -261,6 +277,9 @@ public function test_prune_links_removes_broken_symlinks_in_sites_path() public function test_certificates_trim_tld_for_custom_tlds() { $files = Mockery::mock(Filesystem::class); + $files->shouldReceive('ensureDirExists') + ->once() + ->with('fake-cert-path', user()); $files->shouldReceive('scandir')->once()->andReturn([ 'threeletters.dev.crt', 'fiveletters.local.crt', @@ -280,13 +299,309 @@ public function test_certificates_trim_tld_for_custom_tlds() $this->assertEquals('threeletters', $certs->first()); $this->assertEquals('fiveletters', $certs->last()); } + + + public function test_no_proxies() + { + /** @var FixturesSiteFake $site */ + $site = resolve(FixturesSiteFake::class); + + $site->useOutput(); + + $this->assertEquals([], $site->proxies()->all()); + } + + + public function test_lists_proxies() + { + /** @var FixturesSiteFake $site */ + $site = resolve(FixturesSiteFake::class); + + $site->useFixture('Proxies'); + + $this->assertEquals([ + 'some-proxy.com' => [ + 'site' => 'some-proxy.com', + 'secured' => ' X', + 'url' => 'https://some-proxy.com.test', + 'path' => 'https://127.0.0.1:8443', + ], + 'some-other-proxy.com' => [ + 'site' => 'some-other-proxy.com', + 'secured' => '', + 'url' => 'http://some-other-proxy.com.test', + 'path' => 'https://127.0.0.1:8443', + ], + ], $site->proxies()->all()); + } + + + public function test_add_proxy() + { + swap(CommandLine::class, resolve(CommandLineFake::class)); + + /** @var FixturesSiteFake $site */ + $site = resolve(FixturesSiteFake::class); + + $site->useOutput(); + + $site->assertCertificateNotExists('my-new-proxy.com.test'); + $site->assertNginxNotExists('my-new-proxy.com.test'); + + $site->proxyCreate('my-new-proxy.com', 'https://127.0.0.1:9443'); + + $site->assertCertificateExistsWithCounterValue('my-new-proxy.com.test', 0); + $site->assertNginxExists('my-new-proxy.com.test'); + + $this->assertEquals([ + 'my-new-proxy.com' => [ + 'site' => 'my-new-proxy.com', + 'secured' => ' X', + 'url' => 'https://my-new-proxy.com.test', + 'path' => 'https://127.0.0.1:9443', + ], + ], $site->proxies()->all()); + } + + + public function test_add_proxy_clears_previous_proxy_certificate() + { + swap(CommandLine::class, resolve(CommandLineFake::class)); + + /** @var FixturesSiteFake $site */ + $site = resolve(FixturesSiteFake::class); + + $site->useOutput(); + + $site->proxyCreate('my-new-proxy.com', 'https://127.0.0.1:7443'); + + $site->assertCertificateExistsWithCounterValue('my-new-proxy.com.test', 0); + + $this->assertEquals([ + 'my-new-proxy.com' => [ + 'site' => 'my-new-proxy.com', + 'secured' => ' X', + 'url' => 'https://my-new-proxy.com.test', + 'path' => 'https://127.0.0.1:7443', + ], + ], $site->proxies()->all()); + + // Note: different proxy port + $site->proxyCreate('my-new-proxy.com', 'https://127.0.0.1:9443'); + + // This shows we created a new certificate. + $site->assertCertificateExistsWithCounterValue('my-new-proxy.com.test', 1); + + $this->assertEquals([ + 'my-new-proxy.com' => [ + 'site' => 'my-new-proxy.com', + 'secured' => ' X', + 'url' => 'https://my-new-proxy.com.test', + 'path' => 'https://127.0.0.1:9443', + ], + ], $site->proxies()->all()); + } + + + public function test_add_proxy_clears_previous_non_proxy_certificate() + { + swap(CommandLine::class, resolve(CommandLineFake::class)); + + /** @var FixturesSiteFake $site */ + $site = resolve(FixturesSiteFake::class); + + $site->useOutput(); + + $site->fakeSecure('my-new-proxy.com.test'); + + // For this to test the correct scenario, we need to ensure the + // certificate exists but there is not already a proxy Nginx + // configuration in place. + $site->assertCertificateExistsWithCounterValue('my-new-proxy.com.test', 0); + $site->assertNginxNotExists('my-new-proxy.com.test'); + + $site->proxyCreate('my-new-proxy.com', 'https://127.0.0.1:9443'); + + // This shows we created a new certificate. + $site->assertCertificateExistsWithCounterValue('my-new-proxy.com.test', 1); + + $site->assertNginxExists('my-new-proxy.com.test'); + + $this->assertEquals([ + 'my-new-proxy.com' => [ + 'site' => 'my-new-proxy.com', + 'secured' => ' X', + 'url' => 'https://my-new-proxy.com.test', + 'path' => 'https://127.0.0.1:9443', + ], + ], $site->proxies()->all()); + } + + + public function test_remove_proxy() + { + swap(CommandLine::class, resolve(CommandLineFake::class)); + + /** @var FixturesSiteFake $site */ + $site = resolve(FixturesSiteFake::class); + + $site->useOutput(); + + $site->assertCertificateNotExists('my-new-proxy.com.test'); + $site->assertNginxNotExists('my-new-proxy.com.test'); + + $this->assertEquals([], $site->proxies()->all()); + + $site->proxyCreate('my-new-proxy.com', 'https://127.0.0.1:9443'); + + $this->assertEquals([ + 'my-new-proxy.com' => [ + 'site' => 'my-new-proxy.com', + 'secured' => ' X', + 'url' => 'https://my-new-proxy.com.test', + 'path' => 'https://127.0.0.1:9443', + ], + ], $site->proxies()->all()); + + $site->assertCertificateExists('my-new-proxy.com.test'); + $site->assertNginxExists('my-new-proxy.com.test'); + + $site->proxyDelete('my-new-proxy.com'); + + $site->assertCertificateNotExists('my-new-proxy.com.test'); + $site->assertNginxNotExists('my-new-proxy.com.test'); + + $this->assertEquals([], $site->proxies()->all()); + } +} + + +class CommandLineFake extends CommandLine +{ + public function runCommand($command, callable $onError = null) + { + // noop + // + // This let's us pretend like every command executes correctly + // so we can (elsewhere) ensure the things we meant to do + // (like "create a certificate") look like they + // happened without actually running any + // commands for real. + } +} + + +class FixturesSiteFake extends Site +{ + private $valetHomePath; + private $crtCounter = 0; + + public function valetHomePath() + { + if (! isset($this->valetHomePath)) { + throw new \RuntimeException(static::class.' needs to be configured using useFixtures or useOutput'); + } + + return $this->valetHomePath; + } + + /** + * Use a named fixture (tests/fixtures/[Name]) for this + * instance of the Site. + */ + public function useFixture($fixtureName) + { + $this->valetHomePath = __DIR__.'/fixtures/'.$fixtureName; + } + + /** + * Use the output directory (tests/output) for this instance + * of the Site. + */ + public function useOutput() + { + $this->valetHomePath = __DIR__.'/output'; + } + + public function createCa() + { + // noop + // + // Most of our certificate testing is primitive and not super + // "correct" so we're not going to even bother creating the + // CA for our faked Site. + } + + public function createCertificate($urlWithTld) + { + // We're not actually going to generate a real certificate + // here. We are going to do something basic to include + // the URL and a counter so we can see if this + // method was called when we expect and also + // ensure a file is written out in the + // expected and correct place. + + $crtPath = $this->certificatesPath($urlWithTld, 'crt'); + $keyPath = $this->certificatesPath($urlWithTld, 'key'); + + $counter = $this->crtCounter++; + + file_put_contents($crtPath, 'crt:'.$urlWithTld.':'.$counter); + file_put_contents($keyPath, 'key:'.$urlWithTld.':'.$counter); + } + + public function fakeSecure($urlWithTld) + { + // This method is just used to ensure we all understand that we are + // forcing a fake creation of a URL (including .tld) and passes + // through to createCertificate() directly. + $this->files->ensureDirExists($this->certificatesPath(), user()); + $this->createCertificate($urlWithTld); + } + + public function assertNginxExists($urlWithTld) + { + SiteTest::assertFileExists($this->nginxPath($urlWithTld)); + } + + public function assertNginxNotExists($urlWithTld) + { + SiteTest::assertFileNotExists($this->nginxPath($urlWithTld)); + } + + public function assertCertificateExists($urlWithTld) + { + SiteTest::assertFileExists($this->certificatesPath($urlWithTld, 'crt')); + SiteTest::assertFileExists($this->certificatesPath($urlWithTld, 'key')); + } + + public function assertCertificateNotExists($urlWithTld) + { + SiteTest::assertFileNotExists($this->certificatesPath($urlWithTld, 'crt')); + SiteTest::assertFileNotExists($this->certificatesPath($urlWithTld, 'key')); + } + + public function assertCertificateExistsWithCounterValue($urlWithTld, $counter) + { + // Simple test to assert the certificate for the specified + // URL (including .tld) exists and has the expected + // fake contents. + + $this->assertCertificateExists($urlWithTld); + + $crtPath = $this->certificatesPath($urlWithTld, 'crt'); + $keyPath = $this->certificatesPath($urlWithTld, 'key'); + + SiteTest::assertEquals('crt:'.$urlWithTld.':'.$counter, file_get_contents($crtPath)); + SiteTest::assertEquals('key:'.$urlWithTld.':'.$counter, file_get_contents($keyPath)); + } } class StubForRemovingLinks extends Site { - function sitesPath() + public function sitesPath($additionalPath = null) { - return __DIR__.'/output'; + return __DIR__.'/output'.($additionalPath ? '/'.$additionalPath : ''); } } diff --git a/tests/fixtures/Proxies/Certificates/some-proxy.com.test.crt b/tests/fixtures/Proxies/Certificates/some-proxy.com.test.crt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/Proxies/Nginx/not-a-proxy.com.test b/tests/fixtures/Proxies/Nginx/not-a-proxy.com.test new file mode 100644 index 000000000..1fccc1df2 --- /dev/null +++ b/tests/fixtures/Proxies/Nginx/not-a-proxy.com.test @@ -0,0 +1,90 @@ +# valet stub: proxy.valet.conf + +server { + listen 127.0.0.1:80; + server_name not-a-proxy.com.test www.not-a-proxy.com.test *.not-a-proxy.com.test; + return 301 https://$host$request_uri; +} + +server { + listen 127.0.0.1:443 ssl http2; + server_name not-a-proxy.com.test www.not-a-proxy.com.test *.not-a-proxy.com.test; + root /; + charset utf-8; + client_max_body_size 128M; + http2_push_preload on; + + location /41c270e4-5535-4daa-b23e-c269744c2f45/ { + internal; + alias /; + try_files $uri $uri/; + } + + ssl_certificate "/home/nobody/.config/valet/Certificates/not-a-proxy.com.test.crt"; + ssl_certificate_key "/home/nobody/.config/valet/Certificates/not-a-proxy.com.test.key"; + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + access_log off; + error_log "/home/nobody/.config/valet/Log/not-a-proxy.com.test-error.log"; + + error_page 404 "/home/nobody/.composer/vendor/laravel/valet/server.php"; + + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass php:9000; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_intercept_errors off; + fastcgi_buffer_size 16k; + fastcgi_buffers 4 16k; + fastcgi_param PHP_VALUE "memory_limit = -1"; + } + + location ~ /\.ht { + deny all; + } +} + +server { + listen 127.0.0.1:60; + server_name not-a-proxy.com.test www.not-a-proxy.com.test *.not-a-proxy.com.test; + root /; + charset utf-8; + client_max_body_size 128M; + + add_header X-Robots-Tag 'noindex, nofollow, nosnippet, noarchive'; + + location /41c270e4-5535-4daa-b23e-c269744c2f45/ { + internal; + alias /; + try_files $uri $uri/; + } + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + access_log off; + error_log "/home/nobody/.config/valet/Log/not-a-proxy.com.test-error.log"; + + error_page 404 "/home/nobody/.composer/vendor/laravel/valet/server.php"; + + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass php:9000; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_intercept_errors off; + fastcgi_buffer_size 16k; + fastcgi_buffers 4 16k; + fastcgi_param PHP_VALUE "memory_limit = -1"; + } + + location ~ /\.ht { + deny all; + } +} + diff --git a/tests/fixtures/Proxies/Nginx/some-other-proxy.com.test b/tests/fixtures/Proxies/Nginx/some-other-proxy.com.test new file mode 100644 index 000000000..e988f15cb --- /dev/null +++ b/tests/fixtures/Proxies/Nginx/some-other-proxy.com.test @@ -0,0 +1,95 @@ +# valet stub: proxy.valet.conf + +server { + listen 127.0.0.1:80; + server_name some-other-proxy.com.test www.some-other-proxy.com.test *.some-other-proxy.com.test; + return 301 https://$host$request_uri; +} + +server { + listen 127.0.0.1:443 ssl http2; + server_name some-other-proxy.com.test www.some-other-proxy.com.test *.some-other-proxy.com.test; + root /; + charset utf-8; + client_max_body_size 128M; + http2_push_preload on; + + location /41c270e4-5535-4daa-b23e-c269744c2f45/ { + internal; + alias /; + try_files $uri $uri/; + } + + ssl_certificate "/home/nobody/.config/valet/Certificates/some-other-proxy.com.test.crt"; + ssl_certificate_key "/home/nobody/.config/valet/Certificates/some-other-proxy.com.test.key"; + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + access_log off; + error_log "/home/nobody/.config/valet/Log/some-other-proxy.com.test-error.log"; + + error_page 404 "/home/nobody/.composer/vendor/laravel/valet/server.php"; + + location / { + proxy_pass https://127.0.0.1:8443; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Client-Verify SUCCESS; + proxy_set_header X-Client-DN $ssl_client_s_dn; + proxy_set_header X-SSL-Subject $ssl_client_s_dn; + proxy_set_header X-SSL-Issuer $ssl_client_i_dn; + proxy_set_header X-NginX-Proxy true; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_http_version 1.1; + proxy_read_timeout 1800; + proxy_connect_timeout 1800; + chunked_transfer_encoding on; + proxy_redirect off; + proxy_buffering off; + } + + location ~ /\.ht { + deny all; + } +} + +server { + listen 127.0.0.1:60; + server_name some-other-proxy.com.test www.some-other-proxy.com.test *.some-other-proxy.com.test; + root /; + charset utf-8; + client_max_body_size 128M; + + add_header X-Robots-Tag 'noindex, nofollow, nosnippet, noarchive'; + + location /41c270e4-5535-4daa-b23e-c269744c2f45/ { + internal; + alias /; + try_files $uri $uri/; + } + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + access_log off; + error_log "/home/nobody/.config/valet/Log/some-other-proxy.com.test-error.log"; + + error_page 404 "/home/nobody/.composer/vendor/laravel/valet/server.php"; + + location / { + proxy_pass https://127.0.0.1:8443; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location ~ /\.ht { + deny all; + } +} + diff --git a/tests/fixtures/Proxies/Nginx/some-proxy.com.test b/tests/fixtures/Proxies/Nginx/some-proxy.com.test new file mode 100644 index 000000000..a743f39d3 --- /dev/null +++ b/tests/fixtures/Proxies/Nginx/some-proxy.com.test @@ -0,0 +1,95 @@ +# valet stub: proxy.valet.conf + +server { + listen 127.0.0.1:80; + server_name some-proxy.com.test www.some-proxy.com.test *.some-proxy.com.test; + return 301 https://$host$request_uri; +} + +server { + listen 127.0.0.1:443 ssl http2; + server_name some-proxy.com.test www.some-proxy.com.test *.some-proxy.com.test; + root /; + charset utf-8; + client_max_body_size 128M; + http2_push_preload on; + + location /41c270e4-5535-4daa-b23e-c269744c2f45/ { + internal; + alias /; + try_files $uri $uri/; + } + + ssl_certificate "/home/nobody/.config/valet/Certificates/some-proxy.com.test.crt"; + ssl_certificate_key "/home/nobody/.config/valet/Certificates/some-proxy.com.test.key"; + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + access_log off; + error_log "/home/nobody/.config/valet/Log/some-proxy.com.test-error.log"; + + error_page 404 "/home/nobody/.composer/vendor/laravel/valet/server.php"; + + location / { + proxy_pass https://127.0.0.1:8443; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Client-Verify SUCCESS; + proxy_set_header X-Client-DN $ssl_client_s_dn; + proxy_set_header X-SSL-Subject $ssl_client_s_dn; + proxy_set_header X-SSL-Issuer $ssl_client_i_dn; + proxy_set_header X-NginX-Proxy true; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_http_version 1.1; + proxy_read_timeout 1800; + proxy_connect_timeout 1800; + chunked_transfer_encoding on; + proxy_redirect off; + proxy_buffering off; + } + + location ~ /\.ht { + deny all; + } +} + +server { + listen 127.0.0.1:60; + server_name some-proxy.com.test www.some-proxy.com.test *.some-proxy.com.test; + root /; + charset utf-8; + client_max_body_size 128M; + + add_header X-Robots-Tag 'noindex, nofollow, nosnippet, noarchive'; + + location /41c270e4-5535-4daa-b23e-c269744c2f45/ { + internal; + alias /; + try_files $uri $uri/; + } + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + access_log off; + error_log "/home/nobody/.config/valet/Log/some-proxy.com.test-error.log"; + + error_page 404 "/home/nobody/.composer/vendor/laravel/valet/server.php"; + + location / { + proxy_pass https://127.0.0.1:8443; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location ~ /\.ht { + deny all; + } +} + From 1cd3d238043648ce1267288d5df784ba99ccd2ea Mon Sep 17 00:00:00 2001 From: Beau Simensen Date: Tue, 28 Apr 2020 16:35:19 -0500 Subject: [PATCH 3/5] Update cli/Valet/Site.php Co-Authored-By: Matt Stauffer --- cli/Valet/Site.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/Valet/Site.php b/cli/Valet/Site.php index d497b93b7..a3e0bd791 100644 --- a/cli/Valet/Site.php +++ b/cli/Valet/Site.php @@ -220,7 +220,7 @@ function getSiteConfigFileContents($site) */ function getCertificates($path = null) { - $path = is_null($path) ? $this->certificatesPath() : $path; + $path = $path ?: $this->certificatesPath(); $this->files->ensureDirExists($path, user()); From 02749e9e210fba308f28917f23c3e14985c2f08e Mon Sep 17 00:00:00 2001 From: Beau Simensen Date: Tue, 28 Apr 2020 18:24:18 -0500 Subject: [PATCH 4/5] Add mocks to get tld --- tests/SiteTest.php | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/SiteTest.php b/tests/SiteTest.php index 405f32096..4ef1515a7 100644 --- a/tests/SiteTest.php +++ b/tests/SiteTest.php @@ -314,6 +314,12 @@ public function test_no_proxies() public function test_lists_proxies() { + $config = Mockery::mock(Configuration::class); + $config->shouldReceive('read') + ->andReturn(['tld' => 'test']); + + swap(Configuration::class, $config); + /** @var FixturesSiteFake $site */ $site = resolve(FixturesSiteFake::class); @@ -338,6 +344,12 @@ public function test_lists_proxies() public function test_add_proxy() { + $config = Mockery::mock(Configuration::class); + $config->shouldReceive('read') + ->andReturn(['tld' => 'test']); + + swap(Configuration::class, $config); + swap(CommandLine::class, resolve(CommandLineFake::class)); /** @var FixturesSiteFake $site */ @@ -366,6 +378,12 @@ public function test_add_proxy() public function test_add_proxy_clears_previous_proxy_certificate() { + $config = Mockery::mock(Configuration::class); + $config->shouldReceive('read') + ->andReturn(['tld' => 'test']); + + swap(Configuration::class, $config); + swap(CommandLine::class, resolve(CommandLineFake::class)); /** @var FixturesSiteFake $site */ @@ -405,6 +423,12 @@ public function test_add_proxy_clears_previous_proxy_certificate() public function test_add_proxy_clears_previous_non_proxy_certificate() { + $config = Mockery::mock(Configuration::class); + $config->shouldReceive('read') + ->andReturn(['tld' => 'test']); + + swap(Configuration::class, $config); + swap(CommandLine::class, resolve(CommandLineFake::class)); /** @var FixturesSiteFake $site */ @@ -440,6 +464,12 @@ public function test_add_proxy_clears_previous_non_proxy_certificate() public function test_remove_proxy() { + $config = Mockery::mock(Configuration::class); + $config->shouldReceive('read') + ->andReturn(['tld' => 'test']); + + swap(Configuration::class, $config); + swap(CommandLine::class, resolve(CommandLineFake::class)); /** @var FixturesSiteFake $site */ From 9848bc71b81cab792120b2083e842ffc611d0663 Mon Sep 17 00:00:00 2001 From: Beau Simensen Date: Tue, 28 Apr 2020 18:29:24 -0500 Subject: [PATCH 5/5] Forgot no proxies --- tests/SiteTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/SiteTest.php b/tests/SiteTest.php index 4ef1515a7..cc5229010 100644 --- a/tests/SiteTest.php +++ b/tests/SiteTest.php @@ -303,6 +303,12 @@ public function test_certificates_trim_tld_for_custom_tlds() public function test_no_proxies() { + $config = Mockery::mock(Configuration::class); + $config->shouldReceive('read') + ->andReturn(['tld' => 'test']); + + swap(Configuration::class, $config); + /** @var FixturesSiteFake $site */ $site = resolve(FixturesSiteFake::class);