<?php

include dirname(__FILE__).'/../../../../test/bootstrap/unit.php';
if (!isset($sf_symfony_lib_dir)) {
    $sf_symfony_lib_dir = $configuration->getSymfonyLibDir();
}

require_once dirname(__FILE__).'/../../lib/sfWebBrowser.class.php';

require_once dirname(__FILE__).'/../../lib/sfFopenAdapter.class.php';

require_once dirname(__FILE__).'/../../lib/sfCurlAdapter.class.php';

require_once dirname(__FILE__).'/../../lib/sfSocketsAdapter.class.php';

require_once $sf_symfony_lib_dir.'/exception/sfException.class.php';

require_once dirname(__FILE__).'/../../lib/sfWebBrowserInvalidResponseException.class.php';

require_once $sf_symfony_lib_dir.'/config/sfConfig.class.php';

require_once $sf_symfony_lib_dir.'/util/sfDomCssSelector.class.php';

require_once $sf_symfony_lib_dir.'/util/sfToolkit.class.php';

// Configuration
// -- this script is needed for some tests. It is located in plugin's test/unit/utils folder
$dump_headers_url = 'http://localhost/dumpheaders.php';

// tests
$nb_test_orig = 73;
$adapter_list = ['sfCurlAdapter', 'sfFopenAdapter', 'sfSocketsAdapter'];

// -- sites used for testing requests
$example_site_url = 'http://www.google.com';
$askeet_params = [
    'url' => 'http://www.askeet.com',
    'login' => 'francois',
    'password' => 'llactnevda2',
];

// -- cookies, file and directory automatically created
$cookies_dir = dirname(__FILE__).'/../data/sfCurlAdapter';
$cookies_file = $cookies_dir.'/cookies.txt';

/**
 * stub class.
 */
class myTestWebBrowser extends sfWebBrowser
{
    protected $requestMethod;

    public function call($uri, $method = 'GET', $parameters = [], $headers = [], $changeStack = true)
    {
        parent::call($uri, $method, $parameters, $headers, $changeStack);
        $this->requestMethod = $this->stack[$this->stackPosition]['method'];
    }

    public function getRequestMethod()
    {
        return $this->requestMethod;
    }
}

$t = new lime_test($nb_test_orig * count($adapter_list), new lime_output_color());
foreach ($adapter_list as $adapter) {
    $t->diag('Testing '.$adapter);
    $t->diag('');

    // Initialization

    $t->diag('Initialization');
    $b = new sfWebBrowser([], $adapter);

    $t->is($b->getUserAgent(), '', 'a new browser has an empty user agent');
    $t->is($b->getResponseText(), '', 'a new browser has an empty response');
    $t->is($b->getResponseCode(), '', 'a new browser has an empty response code');
    $t->is($b->getResponseHeaders(), [], 'a new browser has empty reponse headers');

    // Utility methods

    $t->diag('Utility methods');
    $b = new sfWebBrowser([], $adapter);
    $t->is($b->setUserAgent('foo bar')->getUserAgent(), 'foo bar', 'setUserAgent() sets the user agent');
    $t->is($b->setResponseText('foo bar')->getResponseText(), 'foo bar', 'setResponseText() extracts the response');
    $t->is($b->setResponseCode('foo 123 bar')->getResponseCode(), '123', 'setResponseCode() extracts the three-digits code');
    $t->is($b->setResponseCode('foo 12 bar')->getResponseCode(), '', 'setResponseCode() fails silently when response status is incorrect');
    $t->is_deeply($b->setResponseHeaders(['HTTP1.1 200 OK', 'foo: bar', 'bar: baz'])->getResponseHeaders(), ['Foo' => 'bar', 'Bar' => 'baz'], 'setResponseHeaders() extracts the headers array');
    $t->is_deeply($b->setResponseHeaders(['ETag: "535a8-9fb-44ff4a13"', 'WWW-Authenticate: Basic realm="Myself"'])->getResponseHeaders(), ['ETag' => '"535a8-9fb-44ff4a13"', 'WWW-Authenticate' => 'Basic realm="Myself"'], 'setResponseHeaders() extracts the headers array and accepts response headers with several uppercase characters');
    $t->is_deeply($b->setResponseHeaders(['HTTP1.1 200 OK', 'foo: bar', 'bar:baz', 'baz:bar'])->getResponseHeaders(), ['Foo' => 'bar'], 'setResponseHeaders() ignores malformed headers');

    // Exceptions

    $t->diag('Exceptions');
    $b = new sfWebBrowser([], $adapter);

    try {
        $b->get('htp://askeet');
        $t->fail('get() throws an exception when passed an uri which is neither http nor https');
    } catch (Exception $e) {
        $t->pass('get() throws an exception when passed an uri which is neither http nor https');
    }

    // Simple GET request

    $t->diag('Simple GET request');
    $t->like($b->get($dump_headers_url)->getResponseText(), '/\[REQUEST_METHOD\] => GET/', 'get() performs a GET request');
    $t->isa_ok($b, 'sfWebBrowser', 'get() make a web request and returns a browser object');
    $t->is($b->getResponseCode(), 200, 'get() fills up the browser status code with the response');
    $t->like($b->get($example_site_url)->getResponseHeader('Content-Type'), '/text\/html/', 'get() populates the header array');
    $t->like(strtolower($b->getResponseText()), '/<head>/', 'get() populates the HTML of the response');

    // Simple HEAD request

    $t->diag('Simple HEAD request');
    $t->like($b->head($dump_headers_url)->getResponseHeader('Content-Type'), '/text\/html/', 'head() populates the header array');
    $t->is($b->getResponseText(), '', 'HEAD requests do not return a response body');

    // Simple POST request

    $t->diag('Simple POST request');
    $t->like($b->post($dump_headers_url)->getResponseText(), '/\[REQUEST_METHOD\] => POST/', 'post() performs a POST request');
    $t->like($b->post($dump_headers_url, ['post body'])->getResponseText(), '/post body/', 'post() sends body to server');

    // Simple PUT request

    $t->diag('Simple PUT request');
    $t->like($b->put($dump_headers_url)->getResponseText(), '/\[REQUEST_METHOD\] => PUT/', 'put() performs a PUT request');
    $t->like($b->put($dump_headers_url, ['PUT body'])->getResponseText(), '/PUT body/', 'put() sends body to server');

    // Simple DELETE request

    $t->diag('Simple DELETE request');
    $t->like($b->delete($dump_headers_url)->getResponseText(), '/\[REQUEST_METHOD\] => DELETE/', 'delete() performs a DELETE request');

    // Arbitrary request

    $t->diag('Arbitrary request');
    $t->like($b->call($dump_headers_url, 'MICHEL')->getResponseText(), '/\[REQUEST_METHOD\] => MICHEL/', 'call() supports any HTTP methods');

    // Response formats methods

    $t->diag('Response formats methods');
    $b = new sfWebBrowser([], $adapter);
    $b->get($example_site_url);
    $t->like($b->getResponseText(), '/<body .*>/', 'getResponseText() returns the response text');
    $t->unlike($b->getResponseBody(), '/<body>/', 'getResponseBody() returns the response body');
    $t->isa_ok($b->getResponseDom(), 'DOMDocument', 'getResponseDom() returns the response Dom');
    $t->isa_ok($b->getResponseDomCssSelector(), 'sfDomCssSelector', 'getResponseDomCssSelector() returns a CSS selector on the response Dom');
    $b->get('http://rss.cnn.com/rss/cnn_topstories.rss');
    $t->isa_ok($b->getResponseXml(), 'SimpleXMLElement', 'getResponseXml() returns the response as a SimpleXML Element');
    $b->get('http://www.w3.org/StyleSheets/home.css');

    try {
        $b->getResponseXml();
        $t->fail('Incorrect XML throws an exception');
    } catch (Exception $e) {
        $t->pass('Incorrect XML throws an exception');
    }

    try {
        // Absolute and relative URls

        $t->diag('Absolute and relative URls');
        $b = new sfWebBrowser([], $adapter);
        $t->like($b->get($askeet_params['url'])->getResponseText(), '/<h1>featured questions<\/h1>/', 'get() understands absolute urls');
        $t->like($b->get($askeet_params['url'].'/index/1')->getResponseText(), '/<h1>popular questions<\/h1>/', 'get() understands absolute urls');
        $t->like($b->get('/recent/1')->getResponseText(), '/<h1>recent questions<\/h1>/', 'get() understands relative urls with a trailing slash');
        $t->like($b->get('/')->get('recent/1')->getResponseText(), '/<h1>recent questions<\/h1>/', 'get() understands relative urls without a trailing slash');

        // Interaction methods

        $t->diag('Interaction methods');
        $b = new sfWebBrowser([], $adapter);
        $t->like($b->get($askeet_params['url'])->click('activities')->getResponseText(), '/tag "activities"/', 'click() clicks on a link and executes the related request');
        $t->like($b->get($askeet_params['url'])->click('/tag/activities')->getResponseText(), '/tag "activities"/', 'click() clicks on a link and executes the related request');
        $t->like($b->click('askeet')->getResponseText(), '/<h1>featured questions<\/h1>/', 'click() clicks on an image if it finds the argument in the alt');
        $t->like($b->click('search it', ['search' => 'foo'])->getResponseText(), '/<h1>questions matching "foo"<\/h1>/', 'click() clicks on a form input');
        $t->like($b->setField('search', 'bar')->click('search it')->getResponseText(), '/<h1>questions matching "bar"<\/h1>/', 'setField() fills a form input');
    } catch (Exception $e) {
        $t->fail(sprintf('%s : skipping askeet related tests', $e->getMessage()));
    }

    // GET request with parameters

    $t->diag('GET request with parameters');
    $b = new sfWebBrowser([], $adapter);
    $test_params = ['foo' => 'bar', 'baz' => '1'];
    $t->like($b->get($dump_headers_url, $test_params)->getResponseText(), '/\?foo=bar&baz=1/', 'get() can pass parameters with the second argument');
    $t->like($b->get($dump_headers_url.'?'.http_build_query($test_params))->getResponseText(), '/\?foo=bar&baz=1/', 'get() can pass parameters concatenated to the URI as a query string');
    $t->unlike($b->get($dump_headers_url.'?'.http_build_query($test_params))->getResponseText(), '/\?foo=bar&baz=1\&/', 'get() with an URL already containing request parameters doesn\'t add an extra &');
    $t->like($b->get($dump_headers_url.'?'.http_build_query($test_params), ['biz' => 'bil'])->getResponseText(), '/\?foo=bar&baz=1&biz=bil/', 'get() can pass parameters concatenated to the URI as a query string and other parameters as a second argument');

    $b = new sfWebBrowser([], $adapter);
    $b->get($dump_headers_url);

    // Default request headers

    $t->diag('Default request headers');
    $headers = ['Accept-language' => 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3'];
    $b = new sfWebBrowser($headers, $adapter);
    $t->like(
        $b->get($dump_headers_url)->getResponseText(),
        '/fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3/',
        'sfWebBrowser constructor accepts default request headers as first parameter'
    );
    $headers = ['Accept-language' => 'en-gb;q=0.8,en-us;q=0.5,en;q=0.3'];
    $t->like(
        $b->get($dump_headers_url, [], $headers)->getResponseText(),
        '/en-gb;q=0.8,en-us;q=0.5,en;q=0.3/',
        'Default request headers are overriden by request specific headers'
    );

    // Request headers support

    $t->diag('Request headers support');
    $b = new sfWebBrowser([], $adapter);
    $headers = [
        'Accept-language' => 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3',
        'Accept' => 'text/xml',
    ];
    $t->like(
        $b->get($dump_headers_url, [], $headers)->getResponseText(),
        '/fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3/',
        'get() can pass request headers with the third argument'
    );
    $t->like(
        $b->post($dump_headers_url, [], $headers)->getResponseText(),
        '/fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3/',
        'post() can pass request headers with the third argument'
    );
    $msg = 'get() can pass request headers not common that are defined uppercase in RFC 2616';

    try {
        $t->like(
            $b->get($dump_headers_url, [], ['TE' => 'trailers, deflate;q=0.5'])->getResponseText(),
            '/\\[TE\\] => trailers, deflate;q=0.5/',
            $msg
        );
    } catch (Exception $e) {
        $t->fail($msg);
    }

    $msg = 'get() can pass request headers not common that are IE7 dependent: see http://www.w3.org/2006/http-header, now: ';
    $field = '';

    try {
        $headers = ['UA-CPU' => 'x86', 'UA-OS' => 'MacOS', 'UA-Color' => 'color16', 'UA-Pixels' => '240x320'];
        $resp = $b->get($dump_headers_url, [], $headers)->getResponseText();
        foreach ($headers as $field => $value) {
            $t->like($resp, "/\\[{$field}\\] => {$value}/", $msg.$field);
        }
    } catch (Exception $e) {
        $t->fail($msg.field.' - header refused');
    }

    // Encoded response body support

    $t->diag('Encoded response body support');

    $headers = ['Accept-Encoding' => 'gzip'];
    $t->like(
        $b->get($dump_headers_url, [], $headers)->getResponseText(),
        '/gzip/',
        'getResponseText() can decode gzip encoded response body'
    );
    $headers = ['Accept-Encoding' => 'deflate'];
    $t->like(
        $b->get($dump_headers_url, [], $headers)->getResponseText(),
        '/deflate/',
        'getResponseText() can decode deflate encoded response body'
    );

    $encodings = [];
    if (function_exists('gzuncompress')) {
        $encodings[] = 'deflate';
    }
    if (function_exists('gzinflate')) {
        $encodings[] = 'gzip';
    }
    $target_headers = implode(',', $encodings);
    $t->like(
        $b->get($dump_headers_url, [], $headers)->getResponseText(),
        "/{$target_headers}/",
        'sfWebBrowser autosets accept-encoding headers depending on php capabilities'
    );

    $encodings = [];
    if (function_exists('gzinflate')) {
        $encodings[] = 'gzip';
    }
    if (function_exists('gzuncompress')) {
        $encodings[] = 'deflate';
    }
    $headers = ['accept-encoding' => 'bzip2'];
    array_unshift($encodings, 'bzip2');
    $target_headers = implode(',', $encodings);
    $t->like(
        $b->get($dump_headers_url, [], $headers)->getResponseText(),
        "/{$target_headers}/",
        'it is possible to set supplementary encodings'
    );

    // History methods

    $t->diag('History methods');
    $b = new sfWebBrowser([], $adapter);
    $b->get($dump_headers_url);
    $b->get($dump_headers_url.'?foo=bar');
    $b->back();
    $t->unlike($b->getResponseText(), '/foo=bar/', 'back() executes again the previous request in the history');
    $b->forward();
    $t->like($b->getResponseText(), '/foo=bar/', 'forward() executes again the next request in the history');
    $b->reload();
    $t->like($b->getResponseText(), '/foo=bar/', 'reload() executes again the current request in the history');

    // Error management

    $t->diag('Error management');

    try {
        $b->get('http://nonexistent');
        $t->fail('an exception is thrown when an adapter error occurs');
    } catch (Exception $e) {
        $t->pass('an exception is thrown when an adapter error occurs');
    }

    $t->is($b->get($example_site_url.'/nonexistentpage.html')->responseIsError(), true, 'responseIsError() returns true when response is an error');
    $t->is($b->get($example_site_url)->responseIsError(), false, 'responseIsError() returns false when response is not an error');

    // Browser restart

    $t->diag('Browser restart');
    $b->restart();

    try {
        $b->reload();
        $t->fail('restart() reinitializes the browser history');
    } catch (Exception $e) {
        $t->pass('restart() reinitializes the browser history');
    }
    $t->is($b->getResponseText(), '', 'restart() reinitializes the response');

    // Redirects

    $t->diag('Redirects');
    $b = new sfWebBrowser([], $adapter);
    $b->get('http://www.symfony-project.com/trac/wiki/sfUJSPlugin');
    $t->like($b->getResponseText(), '/learn more about the unobtrusive approach/', 'follows 302 redirect after a GET');

    $b = new myTestWebBrowser([], $adapter);
    $b->call($askeet_params['url'].'/index.php/login', 'POST', ['nickname' => $askeet_params['login'], 'password' => $askeet_params['password']]);
    // $t->like($b->getResponseText(), '/url='.preg_quote($askeet_params['url'], '/').'\/index\.php/', 'does NOT follow a 302 redirect after a POST');
    $t->like($b->getResponseText(), '/featured questions/', 'follows 302 redirect after POST ****** DESPITE THE HTTP SPEC ******');
    $t->is($b->getRequestMethod(), 'GET', 'request method is changed to GET after POST for 302 redirect ***** DESPITE THE HTTP SPEC *****');
    $t->todo('request method is changed to GET after POST for 303 redirect');

    // Cookies

    $t->diag('Cookies');
    if ('sfCurlAdapter' == $adapter) {
        $b = new sfWebBrowser([], $adapter, [
            'cookies' => true,
            'cookies_file' => $cookies_file,
            'cookies_dir' => $cookies_dir,
        ]);
        $b->call($askeet_params['url'].'/login', 'POST', [
            'nickname' => $askeet_params['login'],
            'password' => $askeet_params['password'],
        ]);
        $t->like($b->getResponseBody(), '/'.$askeet_params['login'].' profile/', 'Cookies can be added to the request');

        rmdir($cookies_dir);
        rmdir(dirname(__FILE__).'/../data');
    } else {
        $t->todo('Cookies can be added to the request (sfCurlAdapter only for now)');
    }

    // File Uploads

    $t->diag('File uploads');
    if ('sfCurlAdapter' == $adapter) {
        $b->post($dump_headers_url, [
            'test_file' => realpath(__FILE__),
        ]);
        $t->like($b->getResponseText(), '/\[test_file\]/', 'The request can upload a file');
    } else {
        $t->todo('The request can upload a file (sfCurlAdapter only for now)');
    }

    // Soap requests

    $t->diag('Soap requests');
    $url = 'http://www.abundanttech.com/WebServices/Population/population.asmx';
    $headers = [
        'Soapaction' => 'http://www.abundanttech.com/WebServices/Population/getWorldPopulation',
        'Content-Type' => 'text/xml',
    ];
    $requestBody = <<<'EOT'
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <getWorldPopulation xmlns="http://www.abundanttech.com/WebServices/Population" />
  </soap:Body>
</soap:Envelope>
EOT;
    $b = new sfWebBrowser([], $adapter);
    $b->post($url, $requestBody, $headers);
    $t->like($b->getResponseText(), '/<Country>World<\/Country>/', 'sfWebBrowser can make a low-level SOAP call without parameter');

    $url = 'http://www.abundanttech.com/WebServices/Population/population.asmx';
    $headers = [
        'Soapaction' => 'http://www.abundanttech.com/WebServices/Population/getPopulation',
        'Content-Type' => 'text/xml',
    ];
    $requestBody = <<<'EOT'
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:pop="http://www.abundanttech.com/WebServices/Population">
  <soapenv:Header/>
  <soapenv:Body>
    <pop:getPopulation>
      <pop:strCountry>Comoros</pop:strCountry>
    </pop:getPopulation>
  </soapenv:Body>
</soapenv:Envelope>
EOT;
    $b = new sfWebBrowser([], $adapter);
    $b->post($url, $requestBody, $headers);
    $t->like($b->getResponseText(), '/<Country>Comoros<\/Country>/', 'sfWebBrowser can make a low-level SOAP call with parameter');

    $t->diag('');
}
