use Mojo::Base -strict;

use Test::More;
use Mojo::Asset::File;
use Mojo::Content::Single;
use Mojo::Content::MultiPart;
use Mojo::File 'tempdir';
use Mojo::JSON 'encode_json';
use Mojo::Message::Response;
use Mojo::Util qw(encode gzip);

# Defaults
my $res = Mojo::Message::Response->new;
is $res->max_message_size, 2147483648, 'right default';

# Common status codes
$res = Mojo::Message::Response->new;
is $res->code(100)->default_message, 'Continue',            'right message';
is $res->code(101)->default_message, 'Switching Protocols', 'right message';
is $res->code(102)->default_message, 'Processing',          'right message';
is $res->code(103)->default_message, 'Early Hints',         'right message';
is $res->code(200)->default_message, 'OK',                  'right message';
is $res->code(201)->default_message, 'Created',             'right message';
is $res->code(202)->default_message, 'Accepted',            'right message';
is $res->code(203)->default_message, 'Non-Authoritative Information',
  'right message';
is $res->code(204)->default_message, 'No Content',         'right message';
is $res->code(205)->default_message, 'Reset Content',      'right message';
is $res->code(206)->default_message, 'Partial Content',    'right message';
is $res->code(207)->default_message, 'Multi-Status',       'right message';
is $res->code(208)->default_message, 'Already Reported',   'right message';
is $res->code(226)->default_message, 'IM Used',            'right message';
is $res->code(300)->default_message, 'Multiple Choices',   'right message';
is $res->code(301)->default_message, 'Moved Permanently',  'right message';
is $res->code(302)->default_message, 'Found',              'right message';
is $res->code(303)->default_message, 'See Other',          'right message';
is $res->code(304)->default_message, 'Not Modified',       'right message';
is $res->code(305)->default_message, 'Use Proxy',          'right message';
is $res->code(307)->default_message, 'Temporary Redirect', 'right message';
is $res->code(308)->default_message, 'Permanent Redirect', 'right message';
is $res->code(400)->default_message, 'Bad Request',        'right message';
is $res->code(401)->default_message, 'Unauthorized',       'right message';
is $res->code(402)->default_message, 'Payment Required',   'right message';
is $res->code(403)->default_message, 'Forbidden',          'right message';
is $res->code(404)->default_message, 'Not Found',          'right message';
is $res->code(405)->default_message, 'Method Not Allowed', 'right message';
is $res->code(406)->default_message, 'Not Acceptable',     'right message';
is $res->code(407)->default_message, 'Proxy Authentication Required',
  'right message';
is $res->code(408)->default_message, 'Request Timeout',     'right message';
is $res->code(409)->default_message, 'Conflict',            'right message';
is $res->code(410)->default_message, 'Gone',                'right message';
is $res->code(411)->default_message, 'Length Required',     'right message';
is $res->code(412)->default_message, 'Precondition Failed', 'right message';
is $res->code(413)->default_message, 'Request Entity Too Large',
  'right message';
is $res->code(414)->default_message, 'Request-URI Too Long',   'right message';
is $res->code(415)->default_message, 'Unsupported Media Type', 'right message';
is $res->code(416)->default_message, 'Request Range Not Satisfiable',
  'right message';
is $res->code(417)->default_message, 'Expectation Failed',    'right message';
is $res->code(418)->default_message, "I'm a teapot",          'right message';
is $res->code(421)->default_message, 'Misdirected Request',   'right message';
is $res->code(422)->default_message, 'Unprocessable Entity',  'right message';
is $res->code(423)->default_message, 'Locked',                'right message';
is $res->code(424)->default_message, 'Failed Dependency',     'right message';
is $res->code(425)->default_message, 'Unordered Collection',  'right message';
is $res->code(426)->default_message, 'Upgrade Required',      'right message';
is $res->code(428)->default_message, 'Precondition Required', 'right message';
is $res->code(429)->default_message, 'Too Many Requests',     'right message';
is $res->code(431)->default_message, 'Request Header Fields Too Large',
  'right message';
is $res->code(451)->default_message, 'Unavailable For Legal Reasons',
  'right message';
is $res->code(500)->default_message, 'Internal Server Error', 'right message';
is $res->code(501)->default_message, 'Not Implemented',       'right message';
is $res->code(502)->default_message, 'Bad Gateway',           'right message';
is $res->code(503)->default_message, 'Service Unavailable',   'right message';
is $res->code(504)->default_message, 'Gateway Timeout',       'right message';
is $res->code(505)->default_message, 'HTTP Version Not Supported',
  'right message';
is $res->code(506)->default_message, 'Variant Also Negotiates', 'right message';
is $res->code(507)->default_message, 'Insufficient Storage',    'right message';
is $res->code(508)->default_message, 'Loop Detected',           'right message';
is $res->code(509)->default_message, 'Bandwidth Limit Exceeded',
  'right message';
is $res->code(510)->default_message, 'Not Extended', 'right message';
is $res->code(511)->default_message, 'Network Authentication Required',
  'right message';
is $res->default_message(100), 'Continue', 'right message';

# Status code ranges
ok $res->code(101)->is_info,         'is in range';
ok $res->code(199)->is_info,         'is in range';
ok $res->code(200)->is_success,      'is in range';
ok $res->code(202)->is_success,      'is in range';
ok $res->code(299)->is_success,      'is in range';
ok $res->code(301)->is_redirect,     'is in range';
ok $res->code(399)->is_redirect,     'is in range';
ok $res->code(401)->is_client_error, 'is in range';
ok $res->code(499)->is_client_error, 'is in range';
ok $res->code(400)->is_error,        'is in range';
ok $res->code(599)->is_error,        'is in range';
ok $res->code(501)->is_server_error, 'is in range';
ok $res->code(599)->is_server_error, 'is in range';
ok !$res->code(200)->is_info,         'not in range';
ok !$res->code(199)->is_success,      'not in range';
ok !$res->code(300)->is_success,      'not in range';
ok !$res->code(200)->is_redirect,     'not in range';
ok !$res->code(200)->is_error,        'not in range';
ok !$res->code(200)->is_client_error, 'not in range';
ok !$res->code(200)->is_server_error, 'not in range';
ok !$res->code(undef)->is_success,    'no range';

# Status code and message
$res = Mojo::Message::Response->new;
is $res->code,            undef,       'no status';
is $res->default_message, 'Not Found', 'right default message';
is $res->message,         undef,       'no message';
$res->message('Test');
is $res->message, 'Test', 'right message';
$res->code(500);
is $res->code,            500,                     'right status';
is $res->message,         'Test',                  'right message';
is $res->default_message, 'Internal Server Error', 'right default message';
$res = Mojo::Message::Response->new;
is $res->code(400)->default_message, 'Bad Request', 'right default message';
$res = Mojo::Message::Response->new;
is $res->code(1)->default_message, '', 'empty default message';

# Parse HTTP 1.1 response start-line, no headers and body
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 200 OK\x0d\x0a\x0d\x0a");
ok !$res->is_finished, 'response is not finished';
is $res->code,    200,   'right status';
is $res->message, 'OK',  'right message';
is $res->version, '1.1', 'right version';

# Parse HTTP 1.1 response start-line, no headers and body (small chunks)
$res = Mojo::Message::Response->new;
$res->parse('H');
ok !$res->is_finished, 'response is not finished';
$res->parse('T');
ok !$res->is_finished, 'response is not finished';
$res->parse('T');
ok !$res->is_finished, 'response is not finished';
$res->parse('P');
ok !$res->is_finished, 'response is not finished';
$res->parse('/');
ok !$res->is_finished, 'response is not finished';
$res->parse('1');
ok !$res->is_finished, 'response is not finished';
$res->parse('.');
ok !$res->is_finished, 'response is not finished';
$res->parse('1');
ok !$res->is_finished, 'response is not finished';
$res->parse(' ');
ok !$res->is_finished, 'response is not finished';
$res->parse('2');
ok !$res->is_finished, 'response is not finished';
$res->parse('0');
ok !$res->is_finished, 'response is not finished';
$res->parse('0');
ok !$res->is_finished, 'response is not finished';
$res->parse(' ');
ok !$res->is_finished, 'response is not finished';
$res->parse('O');
ok !$res->is_finished, 'response is not finished';
$res->parse('K');
ok !$res->is_finished, 'response is not finished';
$res->parse("\x0d");
ok !$res->is_finished, 'response is not finished';
$res->parse("\x0a");
ok !$res->is_finished, 'response is not finished';
$res->parse("\x0d");
ok !$res->is_finished, 'response is not finished';
$res->parse("\x0a");
ok !$res->is_finished, 'response is not finished';
is $res->code,    200,   'right status';
is $res->message, 'OK',  'right message';
is $res->version, '1.1', 'right version';

# Parse HTTP 1.1 response start-line, no headers and body (no message)
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 200\x0d\x0a\x0d\x0a");
ok !$res->is_finished, 'response is not finished';
is $res->code,    200,   'right status';
is $res->message, undef, 'no message';
is $res->version, '1.1', 'right version';

# Parse HTTP 1.0 response start-line and headers but no body
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.0 404 Damn it\x0d\x0a");
$res->parse("Content-Type: text/plain\x0d\x0a");
$res->parse("Content-Length: 0\x0d\x0a\x0d\x0a");
ok $res->is_finished, 'response is finished';
is $res->code,        404, 'right status';
is $res->message,     'Damn it', 'right message';
is $res->version,     '1.0', 'right version';
is $res->headers->content_type,   'text/plain', 'right "Content-Type" value';
is $res->headers->content_length, 0,            'right "Content-Length" value';

# Parse full HTTP 1.0 response
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.0 500 Internal Server Error\x0d\x0a");
$res->parse("Content-Type: text/plain\x0d\x0a");
$res->parse("Content-Length: 27\x0d\x0a\x0d\x0a");
$res->parse("Hello World!\n1234\nlalalala\n");
ok $res->is_finished, 'response is finished';
is $res->code,        500, 'right status';
is $res->message,     'Internal Server Error', 'right message';
is $res->version,     '1.0', 'right version';
is $res->headers->content_type,   'text/plain', 'right "Content-Type" value';
is $res->headers->content_length, 27,           'right "Content-Length" value';
is $res->body, "Hello World!\n1234\nlalalala\n", 'right content';

# Parse full HTTP 1.0 response (keep-alive)
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.0 500 Internal Server Error\x0d\x0a");
$res->parse("Connection: keep-alive\x0d\x0a\x0d\x0a");
$res->parse("HTTP/1.0 200 OK\x0d\x0a\x0d\x0a");
ok $res->is_finished, 'response is finished';
is $res->code,        500, 'right status';
is $res->message,     'Internal Server Error', 'right message';
is $res->version,     '1.0', 'right version';
is $res->body,        '', 'no content';
is $res->content->leftovers, "HTTP/1.0 200 OK\x0d\x0a\x0d\x0a",
  'next response in leftovers';

# Parse full HTTP 1.0 response (no limit)
{
  local $ENV{MOJO_MAX_MESSAGE_SIZE} = 0;
  $res = Mojo::Message::Response->new;
  is $res->max_message_size, 0, 'right size';
  $res->parse("HTTP/1.0 500 Internal Server Error\x0d\x0a");
  $res->parse("Content-Type: text/plain\x0d\x0a");
  $res->parse("Content-Length: 27\x0d\x0a\x0d\x0a");
  $res->parse("Hello World!\n1234\nlalalala\n");
  ok $res->is_finished, 'response is finished';
  ok !$res->error, 'no error';
  is $res->code,    500,                     'right status';
  is $res->message, 'Internal Server Error', 'right message';
  is $res->version, '1.0',                   'right version';
  is $res->headers->content_type, 'text/plain', 'right "Content-Type" value';
  is $res->headers->content_length, 27, 'right "Content-Length" value';
  is $res->body, "Hello World!\n1234\nlalalala\n", 'right content';
}

# Parse broken start-line
$res = Mojo::Message::Response->new;
$res->parse("12345\x0d\x0a");
ok $res->is_finished, 'response is finished';
is $res->error->{message}, 'Bad response start-line', 'right error';

# Parse full HTTP 1.0 response (missing Content-Length)
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.0 500 Internal Server Error\x0d\x0a");
$res->parse("Content-Type: text/plain\x0d\x0a");
$res->parse("Connection: close\x0d\x0a\x0d\x0a");
$res->parse("Hello World!\n1234\nlalalala\n");
ok !$res->is_finished, 'response is not finished';
is $res->code,    500,                     'right status';
is $res->message, 'Internal Server Error', 'right message';
is $res->version, '1.0',                   'right version';
is $res->headers->content_type,   'text/plain', 'right "Content-Type" value';
is $res->headers->content_length, undef,        'no "Content-Length" value';
is $res->body, "Hello World!\n1234\nlalalala\n", 'right content';

# Parse full HTTP 1.0 response (missing Content-Length and Connection)
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.0 500 Internal Server Error\x0d\x0a");
$res->parse("Content-Type: text/plain\x0d\x0a\x0d\x0a");
$res->parse("Hello World!\n1");
$res->parse("234\nlala");
$res->parse("lala\n");
ok !$res->is_finished, 'response is not finished';
is $res->code,    500,                     'right status';
is $res->message, 'Internal Server Error', 'right message';
is $res->version, '1.0',                   'right version';
is $res->headers->content_type,   'text/plain', 'right "Content-Type" value';
is $res->headers->content_length, undef,        'no "Content-Length" value';
is $res->body, "Hello World!\n1234\nlalalala\n", 'right content';

# Parse full HTTP 1.1 response (missing Content-Length)
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 500 Internal Server Error\x0d\x0a");
$res->parse("Content-Type: text/plain\x0d\x0a");
$res->parse("Connection: close\x0d\x0a\x0d\x0a");
$res->parse("Hello World!\n1234\nlalalala\n");
ok !$res->is_finished, 'response is not finished';
ok !$res->is_empty,    'response is not empty';
ok !$res->content->skip_body, 'body has not been skipped';
ok $res->content->relaxed, 'relaxed response';
is $res->code,    500,                     'right status';
is $res->message, 'Internal Server Error', 'right message';
is $res->version, '1.1',                   'right version';
is $res->headers->content_type,   'text/plain', 'right "Content-Type" value';
is $res->headers->content_length, undef,        'no "Content-Length" value';
is $res->body, "Hello World!\n1234\nlalalala\n", 'right content';

# Parse full HTTP 1.1 response (broken Content-Length)
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 200 OK\x0d\x0a");
$res->parse("Content-Length: 123test\x0d\x0a\x0d\x0a");
$res->parse('Hello World!');
ok $res->is_finished, 'response is finished';
is $res->code,        200, 'right status';
is $res->message,     'OK', 'right message';
is $res->version,     '1.1', 'right version';
is $res->headers->content_length, '123test', 'right "Content-Length" value';
is $res->body, '', 'no content';
is $res->content->leftovers, 'Hello World!', 'content in leftovers';

# Parse full HTTP 1.1 response (100 Continue)
$res = Mojo::Message::Response->new;
$res->content->on(body => sub { shift->headers->header('X-Body' => 'one') });
$res->on(progress => sub { shift->headers->header('X-Progress' => 'two') });
$res->on(finish   => sub { shift->headers->header('X-Finish'   => 'three') });
$res->parse("HTTP/1.1 100 Continue\x0d\x0a\x0d\x0a");
ok $res->is_finished, 'response is finished';
ok $res->is_empty,    'response is empty';
ok $res->content->skip_body, 'body has been skipped';
is $res->code,    100,        'right status';
is $res->message, 'Continue', 'right message';
is $res->version, '1.1',      'right version';
is $res->headers->content_length, undef, 'no "Content-Length" value';
is $res->headers->header('X-Body'),     'one',   'right "X-Body" value';
is $res->headers->header('X-Progress'), 'two',   'right "X-Progress" value';
is $res->headers->header('X-Finish'),   'three', 'right "X-Finish" value';
is $res->body, '', 'no content';

# Parse full HTTP 1.1 response (304 Not Modified)
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 304 Not Modified\x0d\x0a");
$res->parse("Content-Type: text/html\x0d\x0a");
$res->parse("Content-Length: 9000\x0d\x0a");
$res->parse("Connection: keep-alive\x0d\x0a\x0d\x0a");
ok $res->is_finished, 'response is finished';
ok $res->is_empty,    'response is empty';
ok $res->content->skip_body, 'body has been skipped';
is $res->code,    304,            'right status';
is $res->message, 'Not Modified', 'right message';
is $res->version, '1.1',          'right version';
is $res->headers->content_type,   'text/html',  'right "Content-Type" value';
is $res->headers->content_length, 9000,         'right "Content-Length" value';
is $res->headers->connection,     'keep-alive', 'right "Connection" value';
is $res->body, '', 'no content';

# Parse full HTTP 1.1 response (204 No Content)
$res = Mojo::Message::Response->new;
$res->content->on(body => sub { shift->headers->header('X-Body' => 'one') });
$res->on(finish => sub { shift->headers->header('X-Finish' => 'two') });
$res->parse("HTTP/1.1 204 No Content\x0d\x0a");
$res->parse("Content-Type: text/html\x0d\x0a");
$res->parse("Content-Length: 9001\x0d\x0a");
$res->parse("Connection: keep-alive\x0d\x0a\x0d\x0a");
ok $res->is_finished, 'response is finished';
ok $res->is_empty,    'response is empty';
ok $res->content->skip_body, 'body has been skipped';
is $res->code,    204,          'right status';
is $res->message, 'No Content', 'right message';
is $res->version, '1.1',        'right version';
is $res->headers->content_type,   'text/html',  'right "Content-Type" value';
is $res->headers->content_length, 9001,         'right "Content-Length" value';
is $res->headers->connection,     'keep-alive', 'right "Connection" value';
is $res->headers->header('X-Body'),   'one', 'right "X-Body" value';
is $res->headers->header('X-Finish'), 'two', 'right "X-Finish" value';
is $res->body, '', 'no content';

# Parse HTTP 1.1 response (413 error in one big chunk)
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 413 Request Entity Too Large\x0d\x0a"
    . "Connection: Close\x0d\x0a"
    . "Date: Tue, 09 Feb 2010 16:34:51 GMT\x0d\x0a"
    . "Server: Mojolicious (Perl)\x0d\x0a\x0d\x0a");
ok !$res->is_finished, 'response is not finished';
is $res->code,    413,                        'right status';
is $res->message, 'Request Entity Too Large', 'right message';
is $res->version, '1.1',                      'right version';
is $res->headers->content_length, undef, 'right "Content-Length" value';

# Parse HTTP 1.1 chunked response (exceeding limit)
{
  local $ENV{MOJO_MAX_BUFFER_SIZE} = 12;
  $res = Mojo::Message::Response->new;
  is $res->content->max_buffer_size, 12, 'right size';
  $res->parse("HTTP/1.1 200 OK\x0d\x0a");
  $res->parse("Content-Type: text/plain\x0d\x0a");
  $res->parse("Transfer-Encoding: chunked\x0d\x0a\x0d\x0a");
  ok !$res->is_limit_exceeded, 'limit is not exceeded';
  $res->parse('a' x 1000);
  ok $res->is_finished, 'response is finished';
  ok $res->content->is_finished, 'content is finished';
  is $res->error->{message}, 'Maximum buffer size exceeded', 'right error';
  ok $res->is_limit_exceeded, 'limit is not exceeded';
  is $res->code,              200, 'right status';
  is $res->message,           'OK', 'right message';
  is $res->version,           '1.1', 'right version';
  is $res->headers->content_type, 'text/plain', 'right "Content-Type" value';
}

# Parse HTTP 1.1 multipart response (exceeding limit)
{
  local $ENV{MOJO_MAX_BUFFER_SIZE} = 12;
  $res = Mojo::Message::Response->new;
  is $res->content->max_buffer_size, 12, 'right size';
  $res->parse("HTTP/1.1 200 OK\x0d\x0a");
  $res->parse("Content-Length: 420\x0d\x0a");
  $res->parse('Content-Type: multipart/form-data; bo');
  $res->parse("undary=----------0xKhTmLbOuNdArY\x0d\x0a\x0d\x0a");
  ok !$res->content->is_limit_exceeded, 'limit is not exceeded';
  $res->parse('a' x 200);
  ok $res->content->is_limit_exceeded, 'limit is exceeded';
  ok $res->is_finished, 'response is finished';
  ok $res->content->is_finished, 'content is finished';
  is $res->error->{message}, 'Maximum buffer size exceeded', 'right error';
  is $res->code,    200,   'right status';
  is $res->message, 'OK',  'right message';
  is $res->version, '1.1', 'right version';
  is $res->headers->content_type,
    'multipart/form-data; boundary=----------0xKhTmLbOuNdArY',
    'right "Content-Type" value';
}

# Parse HTTP 1.1 gzip compressed response (garbage bytes exceeding limit)
{
  local $ENV{MOJO_MAX_BUFFER_SIZE} = 12;
  $res = Mojo::Message::Response->new;
  is $res->content->max_buffer_size, 12, 'right size';
  $res->parse("HTTP/1.1 200 OK\x0d\x0a");
  $res->parse("Content-Length: 1000\x0d\x0a");
  $res->parse("Content-Encoding: gzip\x0d\x0a\x0d\x0a");
  $res->parse('a' x 5);
  ok !$res->content->is_limit_exceeded, 'limit is not exceeded';
  $res->parse('a' x 995);
  ok $res->content->is_limit_exceeded, 'limit is exceeded';
  ok $res->is_finished, 'response is finished';
  ok $res->content->is_finished, 'content is finished';
  is $res->error->{message}, 'Maximum buffer size exceeded', 'right error';
  is $res->code,    200,   'right status';
  is $res->message, 'OK',  'right message';
  is $res->version, '1.1', 'right version';
  is $res->body,    '',    'no content';
}

# Parse HTTP 1.1 chunked response
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 500 Internal Server Error\x0d\x0a");
$res->parse("Content-Type: text/plain\x0d\x0a");
$res->parse("Transfer-Encoding: chunked\x0d\x0a\x0d\x0a");
$res->parse("4\x0d\x0a");
$res->parse("abcd\x0d\x0a");
$res->parse("9\x0d\x0a");
$res->parse("abcdefghi\x0d\x0a");
$res->parse("0\x0d\x0a\x0d\x0a");
ok $res->is_finished, 'response is finished';
is $res->code,        500, 'right status';
is $res->message,     'Internal Server Error', 'right message';
is $res->version,     '1.1', 'right version';
is $res->headers->content_type,   'text/plain', 'right "Content-Type" value';
is $res->headers->content_length, 13,           'right "Content-Length" value';
is $res->headers->transfer_encoding, undef, 'no "Transfer-Encoding" value';
is $res->body_size, 13, 'right size';

# Parse HTTP 1.1 multipart response
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 200 OK\x0d\x0a");
$res->parse("Content-Length: 420\x0d\x0a");
$res->parse('Content-Type: multipart/form-data; bo');
$res->parse("undary=----------0xKhTmLbOuNdArY\x0d\x0a\x0d\x0a");
$res->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a");
$res->parse("Content-Disposition: form-data; name=\"text1\"\x0d\x0a");
$res->parse("\x0d\x0ahallo welt test123\n");
$res->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a");
$res->parse("Content-Disposition: form-data; name=\"text2\"\x0d\x0a");
$res->parse("\x0d\x0a\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a");
$res->parse('Content-Disposition: form-data; name="upload"; file');
$res->parse("name=\"hello.pl\"\x0d\x0a\x0d\x0a");
$res->parse("Content-Type: application/octet-stream\x0d\x0a\x0d\x0a");
$res->parse("#!/usr/bin/perl\n\n");
$res->parse("use strict;\n");
$res->parse("use warnings;\n\n");
$res->parse("print \"Hello World :)\\n\"\n");
$res->parse("\x0d\x0a------------0xKhTmLbOuNdArY--");
ok $res->is_finished, 'response is finished';
is $res->code,        200, 'right status';
is $res->message,     'OK', 'right message';
is $res->version,     '1.1', 'right version';
is $res->headers->content_type,
  'multipart/form-data; boundary=----------0xKhTmLbOuNdArY',
  'right "Content-Type" value';
ok !$res->content->parts->[0]->is_multipart, 'no multipart content';
ok !$res->content->parts->[1]->is_multipart, 'no multipart content';
ok !$res->content->parts->[2]->is_multipart, 'no multipart content';
is $res->content->parts->[0]->asset->slurp, "hallo welt test123\n",
  'right content';
my $dir  = tempdir;
my $file = $dir->child('multipart.html');
eval { $res->save_to($file) };
like $@, qr/^Multipart content cannot be saved to files/, 'right error';

# Parse HTTP 1.1 chunked multipart response with leftovers (at once)
$res = Mojo::Message::Response->new;
my $multipart
  = "HTTP/1.1 200 OK\x0d\x0a"
  . "Transfer-Encoding: chunked\x0d\x0a"
  . 'Content-Type: multipart/form-data; bo'
  . "undary=----------0xKhTmLbOuNdArY\x0d\x0a\x0d\x0a"
  . "19f\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"
  . "Content-Disposition: form-data; name=\"text1\"\x0d\x0a"
  . "\x0d\x0ahallo welt test123\n"
  . "\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"
  . "Content-Disposition: form-data; name=\"text2\"\x0d\x0a"
  . "\x0d\x0a\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a"
  . 'Content-Disposition: form-data; name="upload"; file'
  . "name=\"hello.pl\"\x0d\x0a"
  . "Content-Type: application/octet-stream\x0d\x0a\x0d\x0a"
  . "#!/usr/bin/perl\n\n"
  . "use strict;\n"
  . "use warnings;\n\n"
  . "print \"Hello World :)\\n\"\n"
  . "\x0d\x0a------------0xKhTmLbOuNdA"
  . "r\x0d\x0a3\x0d\x0aY--\x0d\x0a"
  . "0\x0d\x0a\x0d\x0a"
  . "HTTP/1.0 200 OK\x0d\x0a\x0d\x0a";
$res->parse($multipart);
ok $res->is_finished, 'response is finished';
is $res->code,        200, 'right status';
is $res->message,     'OK', 'right message';
is $res->version,     '1.1', 'right version';
is $res->headers->content_type,
  'multipart/form-data; boundary=----------0xKhTmLbOuNdArY',
  'right "Content-Type" value';
is $res->headers->content_length,    418,   'right "Content-Length" value';
is $res->headers->transfer_encoding, undef, 'no "Transfer-Encoding" value';
is $res->body_size, 418, 'right size';
ok !$res->content->parts->[0]->is_multipart, 'no multipart content';
ok !$res->content->parts->[1]->is_multipart, 'no multipart content';
ok !$res->content->parts->[2]->is_multipart, 'no multipart content';
is $res->content->parts->[0]->asset->slurp, "hallo welt test123\n",
  'right content';
is $res->upload('upload')->filename, 'hello.pl', 'right filename';
ok !$res->upload('upload')->asset->is_file, 'stored in memory';
is $res->upload('upload')->asset->size, 69, 'right size';
is $res->content->parts->[2]->headers->content_type,
  'application/octet-stream', 'right "Content-Type" value';
is $res->content->leftovers, "HTTP/1.0 200 OK\x0d\x0a\x0d\x0a",
  'next response in leftovers';

# Parse HTTP 1.1 chunked multipart response (in multiple small chunks)
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 200 OK\x0d\x0a");
$res->parse("Transfer-Encoding: chunked\x0d\x0a");
$res->parse('Content-Type: multipart/parallel; boundary=AAA; charset=utf-8');
$res->parse("\x0d\x0a\x0d\x0a");
$res->parse("7\x0d\x0a");
$res->parse("--AAA\x0d\x0a");
$res->parse("\x0d\x0a1a\x0d\x0a");
$res->parse("Content-Type: image/jpeg\x0d\x0a");
$res->parse("\x0d\x0a16\x0d\x0a");
$res->parse("Content-ID: 600050\x0d\x0a\x0d\x0a");
$res->parse("\x0d");
$res->parse("\x0a6");
$res->parse("\x0d\x0aabcd\x0d\x0a");
$res->parse("\x0d\x0a7\x0d\x0a");
$res->parse("--AAA\x0d\x0a");
$res->parse("\x0d\x0a1a\x0d\x0a");
$res->parse("Content-Type: image/jpeg\x0d\x0a");
$res->parse("\x0d\x0a16\x0d\x0a");
$res->parse("Content-ID: 600051\x0d\x0a\x0d\x0a");
$res->parse("\x0d\x0a6\x0d\x0a");
$res->parse("efgh\x0d\x0a");
$res->parse("\x0d\x0a7\x0d\x0a");
$res->parse('--AAA--');
ok !$res->is_finished, 'response is not finished';
$res->parse("\x0d\x0a0\x0d\x0a\x0d\x0a");
ok $res->is_finished, 'response is finished';
is $res->code,        200, 'right status';
is $res->message,     'OK', 'right message';
is $res->version,     '1.1', 'right version';
is $res->headers->content_type,
  'multipart/parallel; boundary=AAA; charset=utf-8',
  'right "Content-Type" value';
is $res->headers->content_length,    129,   'right "Content-Length" value';
is $res->headers->transfer_encoding, undef, 'no "Transfer-Encoding" value';
is $res->body_size, 129, 'right size';
ok !$res->content->parts->[0]->is_multipart, 'no multipart content';
ok !$res->content->parts->[1]->is_multipart, 'no multipart content';
is $res->content->parts->[0]->asset->slurp, 'abcd', 'right content';
is $res->content->parts->[0]->headers->content_type, 'image/jpeg',
  'right "Content-Type" value';
is $res->content->parts->[0]->headers->header('Content-ID'), 600050,
  'right "Content-ID" value';
is $res->content->parts->[1]->asset->slurp, 'efgh', 'right content';
is $res->content->parts->[1]->headers->content_type, 'image/jpeg',
  'right "Content-Type" value';
is $res->content->parts->[1]->headers->header('Content-ID'), 600051,
  'right "Content-ID" value';

# Parse HTTP 1.1 multipart response with missing boundary
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 200 OK\x0d\x0a");
$res->parse("Content-Length: 420\x0d\x0a");
$res->parse("Content-Type: multipart/form-data; bo\x0d\x0a\x0d\x0a");
$res->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a");
$res->parse("Content-Disposition: form-data; name=\"text1\"\x0d\x0a");
$res->parse("\x0d\x0ahallo welt test123\n");
$res->parse("\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a");
$res->parse("Content-Disposition: form-data; name=\"text2\"\x0d\x0a");
$res->parse("\x0d\x0a\x0d\x0a------------0xKhTmLbOuNdArY\x0d\x0a");
$res->parse('Content-Disposition: form-data; name="upload"; file');
$res->parse("name=\"hello.pl\"\x0d\x0a\x0d\x0a");
$res->parse("Content-Type: application/octet-stream\x0d\x0a\x0d\x0a");
$res->parse("#!/usr/bin/perl\n\n");
$res->parse("use strict;\n");
$res->parse("use warnings;\n\n");
$res->parse("print \"Hello World :)\\n\"\n");
$res->parse("\x0d\x0a------------0xKhTmLbOuNdArY--");
ok $res->is_finished, 'response is finished';
is $res->code,        200, 'right status';
is $res->message,     'OK', 'right message';
is $res->version,     '1.1', 'right version';
is $res->headers->content_type, 'multipart/form-data; bo',
  'right "Content-Type" value';
ok !$res->content->is_multipart, 'no multipart content';
like $res->content->asset->slurp, qr/hallo welt/, 'right content';

# Parse HTTP 1.1 gzip compressed response
my $uncompressed = 'abc' x 1000;
my $compressed   = gzip $uncompressed;
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 200 OK\x0d\x0a");
$res->parse("Content-Type: text/plain\x0d\x0a");
$res->parse("Content-Length: @{[length $compressed]}\x0d\x0a");
$res->parse("Content-Encoding: GZip\x0d\x0a\x0d\x0a");
ok $res->content->is_compressed, 'content is compressed';
is $res->content->progress, 0, 'right progress';
$res->parse(substr($compressed, 0, 1));
is $res->content->progress, 1, 'right progress';
$res->parse(substr($compressed, 1, length($compressed)));
is $res->content->progress, length($compressed), 'right progress';
ok !$res->content->is_compressed, 'content is not compressed anymore';
ok $res->is_finished, 'response is finished';
ok !$res->error, 'no error';
is $res->code,    200,   'right status';
is $res->message, 'OK',  'right message';
is $res->version, '1.1', 'right version';
is $res->headers->content_type, 'text/plain', 'right "Content-Type" value';
is $res->headers->content_length, length($uncompressed),
  'right "Content-Length" value';
is $res->headers->content_encoding, undef, 'no "Content-Encoding" value';
is $res->body, $uncompressed, 'right content';

# Parse HTTP 1.1 chunked gzip compressed response
$uncompressed = 'abc' x 1000;
$compressed   = undef;
$compressed   = gzip $uncompressed;
$res          = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 200 OK\x0d\x0a");
$res->parse("Content-Type: text/plain\x0d\x0a");
$res->parse("Content-Encoding: gzip\x0d\x0a");
$res->parse("Transfer-Encoding: chunked\x0d\x0a\x0d\x0a");
ok $res->content->is_chunked,    'content is chunked';
ok $res->content->is_compressed, 'content is compressed';
$res->parse("1\x0d\x0a");
$res->parse(substr($compressed, 0, 1));
$res->parse("\x0d\x0a");
$res->parse(sprintf('%x', length($compressed) - 1));
$res->parse("\x0d\x0a");
$res->parse(substr($compressed, 1, length($compressed) - 1));
$res->parse("\x0d\x0a");
$res->parse("0\x0d\x0a\x0d\x0a");
ok !$res->content->is_chunked,    'content is not chunked anymore';
ok !$res->content->is_compressed, 'content is not compressed anymore';
ok $res->is_finished, 'response is finished';
ok !$res->error, 'no error';
is $res->code,    200,   'right status';
is $res->message, 'OK',  'right message';
is $res->version, '1.1', 'right version';
is $res->headers->content_type, 'text/plain', 'right "Content-Type" value';
is $res->headers->content_length, length($uncompressed),
  'right "Content-Length" value';
is $res->headers->transfer_encoding, undef, 'no "Transfer-Encoding" value';
is $res->headers->content_encoding,  undef, 'no "Content-Encoding" value';
is $res->body, $uncompressed, 'right content';

# Build HTTP 1.1 response start-line with minimal headers
$res = Mojo::Message::Response->new;
$res->code(404);
$res->headers->date('Sun, 17 Aug 2008 16:27:35 GMT');
$res = Mojo::Message::Response->new->parse($res->to_string);
ok $res->is_finished, 'response is finished';
is $res->code,        '404', 'right status';
is $res->message,     'Not Found', 'right message';
is $res->version,     '1.1', 'right version';
is $res->headers->date, 'Sun, 17 Aug 2008 16:27:35 GMT', 'right "Date" value';
is $res->headers->content_length, 0, 'right "Content-Length" value';

# Build HTTP 1.1 response start-line with minimal headers (strange message)
$res = Mojo::Message::Response->new;
$res->code(404);
$res->message('Looks-0k!@ ;\':" #$%^<>,.\\o/ &*()');
$res->headers->date('Sun, 17 Aug 2008 16:27:35 GMT');
$res = Mojo::Message::Response->new->parse($res->to_string);
ok $res->is_finished, 'response is finished';
is $res->code,        '404', 'right status';
is $res->message,     'Looks-0k!@ ;\':" #$%^<>,.\\o/ &*()', 'right message';
is $res->version,     '1.1', 'right version';
is $res->headers->date, 'Sun, 17 Aug 2008 16:27:35 GMT', 'right "Date" value';
is $res->headers->content_length, 0, 'right "Content-Length" value';

# Build HTTP 1.1 response start-line and header
$res = Mojo::Message::Response->new;
$res->code(200);
$res->headers->connection('keep-alive');
$res->headers->date('Sun, 17 Aug 2008 16:27:35 GMT');
$res = Mojo::Message::Response->new->parse($res->to_string);
ok $res->is_finished, 'response is finished';
is $res->code,        '200', 'right status';
is $res->message,     'OK', 'right message';
is $res->version,     '1.1', 'right version';
is $res->headers->connection, 'keep-alive', 'right "Connection" value';
is $res->headers->date, 'Sun, 17 Aug 2008 16:27:35 GMT', 'right "Date" value';

# Build full HTTP 1.1 response
$res = Mojo::Message::Response->new;
$res->code(200);
$res->headers->connection('keep-alive');
$res->headers->date('Sun, 17 Aug 2008 16:27:35 GMT');
$res->body("Hello World!\n");
$res = Mojo::Message::Response->new->parse($res->to_string);
ok $res->is_finished, 'response is finished';
is $res->code,        '200', 'right status';
is $res->message,     'OK', 'right message';
is $res->version,     '1.1', 'right version';
is $res->headers->connection, 'keep-alive', 'right "Connection" value';
is $res->headers->date, 'Sun, 17 Aug 2008 16:27:35 GMT', 'right "Date" value';
is $res->headers->content_length, '13', 'right "Content-Length" value';
is $res->body, "Hello World!\n", 'right content';

# Build HTTP 1.1 response parts with progress
$res = Mojo::Message::Response->new;
my ($finished, $state, $progress);
$res->on(finish => sub { $finished = shift->is_finished });
$res->on(
  progress => sub {
    my ($res, $part, $offset) = @_;
    $state = $part;
    $progress += $offset;
  }
);
$res->code(200);
$res->headers->connection('keep-alive');
$res->headers->date('Sun, 17 Aug 2008 16:27:35 GMT');
$res->body("Hello World!\n");
ok !$state,    'no state';
ok !$progress, 'no progress';
ok !$finished, 'not finished';
ok $res->build_start_line, 'built start-line';
is $state, 'start_line', 'made progress on start_line';
ok $progress, 'made progress';
$progress = 0;
ok !$finished, 'not finished';
ok $res->build_headers, 'built headers';
is $state, 'headers', 'made progress on headers';
ok $progress, 'made progress';
$progress = 0;
ok !$finished, 'not finished';
ok $res->build_body, 'built body';
is $state, 'body', 'made progress on headers';
ok $progress, 'made progress';
ok $finished, 'finished';

# Build HTTP 1.1 response with dynamic content
$res = Mojo::Message::Response->new;
$res->code(200);
$res->content->write_chunk(
  'Hello ' => sub {
    shift->write_chunk(undef,
      sub { shift->write_chunk('World!')->write_chunk('') });
  }
);
ok $res->content->is_dynamic, 'dynamic content';
$res = Mojo::Message::Response->new->parse($res->to_string);
ok !$res->content->is_dynamic, 'no dynamic content';
ok $res->is_finished, 'response is finished';
is $res->code,        200, 'right status';
is $res->message,     'OK', 'right message';
is $res->version,     '1.1', 'right version';
is $res->headers->content_length, '12', 'right "Content-Length" value';
is $res->body, 'Hello World!', 'right content';

# Build HTTP 1.1 multipart response
$res = Mojo::Message::Response->new;
$res->content(Mojo::Content::MultiPart->new);
$res->code(200);
$res->headers->content_type('multipart/mixed; boundary=7am1X');
$res->headers->date('Sun, 17 Aug 2008 16:27:35 GMT');
push @{$res->content->parts},
  Mojo::Content::Single->new(asset => Mojo::Asset::File->new);
$res->content->parts->[-1]->asset->add_chunk('Hallo Welt lalalalalala!');
my $content = Mojo::Content::Single->new;
$content->asset->add_chunk("lala\nfoobar\nperl rocks\n");
$content->headers->content_type('text/plain');
push @{$res->content->parts}, $content;
$res = Mojo::Message::Response->new->parse($res->to_string);
ok $res->is_finished, 'response is finished';
is $res->code,        200, 'right status';
is $res->message,     'OK', 'right message';
is $res->version,     '1.1', 'right version';
is $res->headers->date, 'Sun, 17 Aug 2008 16:27:35 GMT', 'right "Date" value';
is $res->headers->content_length, '110', 'right "Content-Length" value';
is $res->headers->content_type, 'multipart/mixed; boundary=7am1X',
  'right "Content-Type" value';
is $res->content->parts->[0]->asset->slurp, 'Hallo Welt lalalalalala!',
  'right content';
is $res->content->parts->[1]->headers->content_type, 'text/plain',
  'right "Content-Type" value';
is $res->content->parts->[1]->asset->slurp, "lala\nfoobar\nperl rocks\n",
  'right content';

# Parse response with cookie
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.0 200 OK\x0d\x0a");
$res->parse("Content-Type: text/plain\x0d\x0a");
$res->parse("Content-Length: 27\x0d\x0a");
$res->parse("Set-Cookie: foo=bar; path=/test\x0d\x0a\x0d\x0a");
$res->parse("Hello World!\n1234\nlalalala\n");
ok $res->is_finished, 'response is finished';
is $res->code,        200, 'right status';
is $res->message,     'OK', 'right message';
is $res->version,     '1.0', 'right version';
is $res->headers->content_type,   'text/plain', 'right "Content-Type" value';
is $res->headers->content_length, 27,           'right "Content-Length" value';
is $res->headers->set_cookie, 'foo=bar; path=/test', 'right "Set-Cookie" value';
my $cookies = $res->cookies;
is $cookies->[0]->name,  'foo',   'right name';
is $cookies->[0]->value, 'bar',   'right value';
is $cookies->[0]->path,  '/test', 'right path';
is $res->cookie('foo')->value, 'bar',   'right value';
is $res->cookie('foo')->path,  '/test', 'right path';

# Parse WebSocket handshake response
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 101 Switching Protocols\x0d\x0a");
$res->parse("Upgrade: websocket\x0d\x0a");
$res->parse("Connection: Upgrade\x0d\x0a");
$res->parse("Sec-WebSocket-Accept: abcdef=\x0d\x0a");
$res->parse("Sec-WebSocket-Protocol: sample\x0d\x0a\x0d\x0a");
ok $res->is_finished, 'response is finished';
ok $res->is_empty,    'response is empty';
ok $res->content->skip_body, 'body has been skipped';
is $res->code,    101,                   'right status';
is $res->message, 'Switching Protocols', 'right message';
is $res->version, '1.1',                 'right version';
is $res->headers->upgrade,    'websocket', 'right "Upgrade" value';
is $res->headers->connection, 'Upgrade',   'right "Connection" value';
is $res->headers->sec_websocket_accept, 'abcdef=',
  'right "Sec-WebSocket-Accept" value';
is $res->headers->sec_websocket_protocol, 'sample',
  'right "Sec-WebSocket-Protocol" value';
is $res->body, '', 'no content';
ok !defined $res->headers->content_length, '"Content-Length" does not exist';

# Parse WebSocket handshake response (with frame)
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 101 Switching Protocols\x0d\x0a");
$res->parse("Upgrade: websocket\x0d\x0a");
$res->parse("Connection: Upgrade\x0d\x0a");
$res->parse("Sec-WebSocket-Accept: abcdef=\x0d\x0a");
$res->parse("Sec-WebSocket-Protocol: sample\x0d\x0a");
$res->parse("\x0d\x0a\x81\x08\x77\x68\x61\x74\x65\x76\x65\x72");
ok $res->is_finished, 'response is finished';
ok $res->is_empty,    'response is empty';
ok $res->content->skip_body, 'body has been skipped';
is $res->code,    101,                   'right status';
is $res->message, 'Switching Protocols', 'right message';
is $res->version, '1.1',                 'right version';
is $res->headers->upgrade,    'websocket', 'right "Upgrade" value';
is $res->headers->connection, 'Upgrade',   'right "Connection" value';
is $res->headers->sec_websocket_accept, 'abcdef=',
  'right "Sec-WebSocket-Accept" value';
is $res->headers->sec_websocket_protocol, 'sample',
  'right "Sec-WebSocket-Protocol" value';
is $res->body, '', 'no content';
is $res->content->leftovers, "\x81\x08\x77\x68\x61\x74\x65\x76\x65\x72",
  'frame in leftovers';
ok !defined $res->headers->content_length, '"Content-Length" does not exist';

# Build WebSocket handshake response
$res = Mojo::Message::Response->new;
$res->code(101);
$res->headers->date('Sun, 17 Aug 2008 16:27:35 GMT');
$res->headers->upgrade('websocket');
$res->headers->connection('Upgrade');
$res->headers->sec_websocket_accept('abcdef=');
$res->headers->sec_websocket_protocol('sample');
$res = Mojo::Message::Response->new->parse($res->to_string);
ok $res->is_finished, 'response is finished';
is $res->code,        '101', 'right status';
is $res->message,     'Switching Protocols', 'right message';
is $res->version,     '1.1', 'right version';
is $res->headers->connection, 'Upgrade', 'right "Connection" value';
is $res->headers->date, 'Sun, 17 Aug 2008 16:27:35 GMT', 'right "Date" value';
ok !defined $res->headers->content_length, '"Content-Length" does not exist';
is $res->headers->upgrade, 'websocket', 'right "Upgrade" value';
is $res->headers->sec_websocket_accept, 'abcdef=',
  'right "Sec-WebSocket-Accept" value';
is $res->headers->sec_websocket_protocol, 'sample',
  'right "Sec-WebSocket-Protocol" value';
is $res->body, '', 'no content';
ok !defined $res->headers->content_length, '"Content-Length" does not exist';

# Build and parse HTTP 1.1 response with 3 cookies
$res = Mojo::Message::Response->new;
$res->code(404);
$res->headers->date('Sun, 17 Aug 2008 16:27:35 GMT');
$res->cookies(
  {name => 'foo', value => 'bar', path => '/foobar'},
  {name => 'bar', value => 'baz', path => '/test/23'}
);
$res->cookies({name => 'baz', value => 'yada', path => '/foobar'});
ok !!$res->to_string, 'message built';
my $res2 = Mojo::Message::Response->new;
$res2->parse($res->to_string);
ok $res2->is_finished, 'response is finished';
is $res2->code,        404, 'right status';
is $res2->version,     '1.1', 'right version';
is $res2->headers->content_length, 0, 'right "Content-Length" value';
ok defined $res2->cookie('foo'),   'cookie "foo" exists';
ok defined $res2->cookie('bar'),   'cookie "bar" exists';
ok defined $res2->cookie('baz'),   'cookie "baz" exists';
ok !defined $res2->cookie('yada'), 'cookie "yada" does not exist';
is $res2->cookie('foo')->path,  '/foobar',  'right path';
is $res2->cookie('foo')->value, 'bar',      'right value';
is $res2->cookie('bar')->path,  '/test/23', 'right path';
is $res2->cookie('bar')->value, 'baz',      'right value';
is $res2->cookie('baz')->path,  '/foobar',  'right path';
is $res2->cookie('baz')->value, 'yada',     'right value';

# Build chunked response body
$res = Mojo::Message::Response->new;
$res->code(200);
my $invocant;
$res->content->write_chunk('hello!' => sub { $invocant = shift });
$res->content->write_chunk('hello world!')->write_chunk('');
ok $res->content->is_chunked, 'chunked content';
ok $res->content->is_dynamic, 'dynamic content';
is $res->build_body,
  "6\x0d\x0ahello!\x0d\x0ac\x0d\x0ahello world!\x0d\x0a0\x0d\x0a\x0d\x0a",
  'right format';
isa_ok $invocant, 'Mojo::Content::Single', 'right invocant';

# Build dynamic response body
$res = Mojo::Message::Response->new;
$res->code(200);
$invocant = undef;
$res->content->write('hello!' => sub { $invocant = shift });
$res->content->write('hello world!')->write('');
ok !$res->content->is_chunked, 'no chunked content';
ok $res->content->is_dynamic, 'dynamic content';
is $res->build_body, "hello!hello world!", 'right format';
isa_ok $invocant, 'Mojo::Content::Single', 'right invocant';

# Build response with callback (make sure it's called)
$res = Mojo::Message::Response->new;
$res->code(200);
$res->headers->content_length(10);
$res->content->write('lala' => sub { die "Body callback was called properly\n" }
);
$res->get_body_chunk(0);
eval { $res->get_body_chunk(3) };
is $@, "Body callback was called properly\n", 'right error';

# Build response with callback (consistency calls)
$res = Mojo::Message::Response->new;
my $body = 'I is here';
$res->headers->content_length(length($body));
my $cb;
$cb = sub { shift->write(substr($body, pop, 1), $cb) };
$res->content->write('' => $cb);
my $full   = '';
my $count  = 0;
my $offset = 0;

while (1) {
  my $chunk = $res->get_body_chunk($offset);
  last unless $chunk;
  $full .= $chunk;
  $offset = length($full);
  $count++;
}
$res->fix_headers;
is $res->headers->connection, undef, 'no "Connection" value';
ok $res->content->is_dynamic, 'dynamic content';
is $count, length($body), 'right length';
is $full, $body, 'right content';

# Build response with callback (no Content-Length header)
$res  = Mojo::Message::Response->new;
$body = 'I is here';
$cb   = sub { shift->write(substr($body, pop, 1), $cb) };
$res->content->write('' => $cb);
$res->fix_headers;
$full   = '';
$count  = 0;
$offset = 0;

while (1) {
  my $chunk = $res->get_body_chunk($offset);
  last unless $chunk;
  $full .= $chunk;
  $offset = length($full);
  $count++;
}
is $res->headers->connection, 'close', 'right "Connection" value';
ok $res->content->is_dynamic, 'dynamic content';
is $count, length($body), 'right length';
is $full, $body, 'right content';

# Body
$res = Mojo::Message::Response->new;
$res->body('hi there!');
ok !$res->content->asset->is_file,      'stored in memory';
ok !$res->content->asset->auto_upgrade, 'no upgrade';
is $res->body, 'hi there!', 'right content';
$res->body('');
is $res->body, '', 'no content';
$res->body('hi there!');
is $res->body, 'hi there!', 'right content';
$res->body(0);
is $res->body, 0, 'right content';
is $res->body('hello!')->body, 'hello!', 'right content';
$res->content(Mojo::Content::MultiPart->new);
$res->body('hi!');
is $res->body, 'hi!', 'right content';

# Text
$res = Mojo::Message::Response->new;
my $snowman = encode 'UTF-8', '☃';
is $res->body($snowman)->text, '☃', 'right content';
is $res->body($snowman)->dom->text, '☃', 'right text';
$res = Mojo::Message::Response->new;
my $yatta = encode 'shift_jis', 'やった';
is $res->body($yatta)->text, $yatta, 'right content';
$res->headers->content_type('text/plain;charset=shift_jis');
is $res->body($yatta)->text, 'やった', 'right content';
is $res->body($yatta)->dom->text, 'やった', 'right text';

# Body exceeding memory limit (no upgrade)
{
  local $ENV{MOJO_MAX_MEMORY_SIZE} = 8;
  $res = Mojo::Message::Response->new;
  $res->body('hi there!');
  is $res->body, 'hi there!', 'right content';
  is $res->content->asset->max_memory_size, 8, 'right size';
  is $res->content->asset->size,            9, 'right size';
  ok !$res->content->asset->is_file, 'stored in memory';
}

# Parse response and extract JSON data
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 200 OK\x0a");
$res->parse("Content-Type: application/json\x0a");
$res->parse("Content-Length: 27\x0a\x0a");
$res->parse(encode_json({foo => 'bar', baz => [1, 2, 3]}));
ok $res->is_finished, 'response is finished';
is $res->code,        200, 'right status';
is $res->message,     'OK', 'right message';
is $res->version,     '1.1', 'right version';
is_deeply $res->json, {foo => 'bar', baz => [1, 2, 3]}, 'right JSON data';
is $res->json('/foo'),   'bar', 'right result';
is $res->json('/baz/1'), 2,     'right result';
is_deeply $res->json('/baz'), [1, 2, 3], 'right result';
$res->json->{baz}[1] = 4;
is_deeply $res->json('/baz'), [1, 4, 3], 'right result';

# Parse response and extract HTML
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 200 OK\x0a");
$res->parse("Content-Type: text/html\x0a");
$res->parse("Content-Length: 51\x0a\x0a");
$res->parse('<p>foo<a href="/">bar</a><a href="/baz">baz</a></p>');
ok $res->is_finished, 'response is finished';
is $res->code,        200, 'right status';
is $res->message,     'OK', 'right message';
is $res->version,     '1.1', 'right version';
is $res->dom->at('p')->text,     'foo', 'right value';
is $res->dom->at('p > a')->text, 'bar', 'right value';
is $res->dom('p')->first->text, 'foo', 'right value';
is_deeply $res->dom('p > a')->map('text')->to_array, [qw(bar baz)],
  'right values';
my @text = $res->dom('a')->map(content => 'yada')->first->root->find('p > a')
  ->map('text')->each;
is_deeply \@text, [qw(yada yada)], 'right values';
is_deeply $res->dom('p > a')->map('text')->to_array, [qw(yada yada)],
  'right values';
@text = $res->dom->find('a')->map(content => 'test')->first->root->find('p > a')
  ->map('text')->each;
is_deeply \@text, [qw(test test)], 'right values';
is_deeply $res->dom->find('p > a')->map('text')->to_array, [qw(test test)],
  'right values';
$file = $dir->child('single.html');
is $res->save_to($file)->body,
  '<p>foo<a href="/">bar</a><a href="/baz">baz</a></p>', 'right content';
is $file->slurp, '<p>foo<a href="/">bar</a><a href="/baz">baz</a></p>',
  'right content';

# Build DOM from response with charset
$res = Mojo::Message::Response->new;
$res->parse("HTTP/1.0 200 OK\x0a");
$res->parse("Content-Type: application/atom+xml; charset=UTF-8; type=feed\x0a");
$res->parse("\x0a");
$res->body('<p>foo <a href="/">bar</a><a href="/baz">baz</a></p>');
ok !$res->is_finished, 'response is not finished';
is $res->headers->content_type,
  'application/atom+xml; charset=UTF-8; type=feed',
  'right "Content-Type" value';
ok $res->dom, 'dom built';
$count = 0;
$res->dom('a')->each(sub { $count++ });
is $count, 2, 'all anchors found';

done_testing();
