From 3dabbbd5325c9fad9582cd44b1da68dece78eaa0 Mon Sep 17 00:00:00 2001 From: Urban Müller Date: Thu, 21 Jun 2018 16:42:32 +0200 Subject: no reason for different naming --- test/U_tests.json | 211 ++++++++++++++++++++ test/autoprepend.t | 38 ++++ test/exec.t | 93 +++++++++ test/getopt.t | 28 +++ test/it.t | 474 +++++++++++++++++++++++++++++++++++++++++++++ test/it_cache.t | 49 +++++ test/it_dbi.t | 364 ++++++++++++++++++++++++++++++++++ test/it_html.t | 291 ++++++++++++++++++++++++++++ test/it_mail.t | 93 +++++++++ test/it_pipe.t | 18 ++ test/it_text.t | 21 ++ test/it_url.t | 315 ++++++++++++++++++++++++++++++ test/it_url.testserver.php | 71 +++++++ test/it_url_server.php | 32 +++ test/it_url_slow.t | 59 ++++++ test/it_xml.t | 127 ++++++++++++ test/itjs.t | 66 +++++++ 17 files changed, 2350 insertions(+) create mode 100644 test/U_tests.json create mode 100755 test/autoprepend.t create mode 100755 test/exec.t create mode 100755 test/getopt.t create mode 100755 test/it.t create mode 100755 test/it_cache.t create mode 100755 test/it_dbi.t create mode 100755 test/it_html.t create mode 100755 test/it_mail.t create mode 100755 test/it_pipe.t create mode 100755 test/it_text.t create mode 100755 test/it_url.t create mode 100644 test/it_url.testserver.php create mode 100644 test/it_url_server.php create mode 100755 test/it_url_slow.t create mode 100755 test/it_xml.t create mode 100755 test/itjs.t (limited to 'test') diff --git a/test/U_tests.json b/test/U_tests.json new file mode 100644 index 0000000..e35ff9d --- /dev/null +++ b/test/U_tests.json @@ -0,0 +1,211 @@ +[ + { + "args": ["/Zürich"], + "exp": "/Z%C3%BCrich", + "name": "U() with utf8 umlaut" + }, + + { + "args": ["/path#Zürich"], + "exp": "/path#Z%C3%BCrich", + "name": "U() with utf8 umlaut in fragment" + }, + + { + "args": ["/foo.html?"], + "exp": "/foo.html", + "name": "U() removes ? at end" + }, + + { + "args": ["/foo.html#"], + "exp": "/foo.html", + "name": "U() removes # at end" + }, + + { + "args": ["/foo.html?#"], + "exp": "/foo.html", + "name": "U() removes ?# at end" + }, + + { + "args": ["/foo.html#?"], + "exp": "/foo.html#?", + "name": "U() keeps #? at end" + }, + + { + "args": ["/foo.html", {"bar": null}], + "exp": "/foo.html", + "name": "U() empty parameter removed" + }, + + { + "args": ["/foo.html?bar="], + "exp": "/foo.html?bar=", + "name": "U() keep empty parameter with = if only base is given" + }, + + { + "args": ["/foo.html?foo=bar&qux"], + "exp": "/foo.html?foo=bar&qux", + "name": "U() keep empty parameter without = if only base is given" + }, + + { + "args": ["/foo.html?bar=", {"foo": "gna"}], + "exp": "/foo.html?foo=gna", + "name": "U() empty parameter with = removed if parameters get changed" + }, + + { + "args": ["/foo.html?foo=bar&qux", {"gna": "gnaber"}], + "exp": "/foo.html?foo=bar&gna=gnaber", + "name": "U() empty parameter without = removed if parameters get changed" + }, + + { + "args": ["/foo.html?bar=gna&foo=g", {"bar": ""}], + "exp": "/foo.html?foo=g", + "name": "U() remove parameter in base by overwriting with empty string" + }, + + { + "args": ["/foo.html?bar=gna&foo=g", {"bar": false}], + "exp": "/foo.html?foo=g", + "name": "U() remove parameter in base by overwriting with false" + }, + + { + "args": ["/foo.html?bar=gna&foo=g", {"bar": null}], + "exp": "/foo.html?bar=gna&foo=g", + "name": "U() keep parameter in base instead of overwriting with null" + }, + + { + "args": ["/foo.html", {"bar": {"gna": 42, "qux": {"quux": "", "gnöp": "fasel"}}}], + "exp": "/foo.html?bar%5Bgna%5D=42&bar%5Bqux%5D%5Bquux%5D=%3CZ%C3%BCrich%3E&bar%5Bqux%5D%5Bgn%C3%B6p%5D=fasel", + "name": "U() with nested arrays" + }, + + { + "args": ["/foo.html?", {"foo" : "bar[qux][gnöp]=fasel"}], + "exp": "/foo.html?foo=bar%5Bqux%5D%5Bgn%C3%B6p%5D%3Dfasel", + "name": "U() with array syntax in value and ? at end of base" + }, + + { + "args": ["/foo.html?a=b&bar=qux&c=d", {"bar": "baz"}], + "exp": "/foo.html?a=b&bar=baz&c=d", + "name": "U() args override get params in base url" + }, + + { + "args": ["/foo.html?a=b&bar=qux&c=d", {"bar": "baz", "gna": "gnaber"}], + "exp": "/foo.html?a=b&bar=baz&c=d&gna=gnaber", + "name": "U() args override get params in base url and appends remaining params" + }, + + { + "args": ["/foo.html?gna=g&a=b&bar=qux&c=d", {"bar": "baz", "gna": "gnaber"}], + "exp": "/foo.html?gna=gnaber&a=b&bar=baz&c=d", + "name": "U() args override get params in base url keeping same order" + }, + + { + "args": ["/foo.html?bar.qux=a.b", {"c.d": "e.f", "g h": "i j"}], + "exp": "/foo.html?bar.qux=a.b&c.d=e.f&g+h=i+j", + "name": "U() dots and spaces in arg names are preserved" + }, + + { + "args": ["/foo.html?bar=a+b+c", {"foo": "d e f"}], + "exp": "/foo.html?bar=a+b+c&foo=d+e+f", + "name": "U() handles multiple spaces in parameter values" + }, + + { + "args": ["/foo.html?bar=qux#frag=frog", {"baz": "gna"}], + "exp": "/foo.html?bar=qux&baz=gna#frag=frog", + "name": "U() fragment after params" + }, + + { + "args": ["Jet d'eau"], + "exp": "Jet%20d%27eau", + "name": "U() with single quotes in URL" + }, + + { + "args": ["/test.html?foo=bar?qux=gna", {"?q": "?r"}], + "exp": "/test.html?foo=bar%3Fqux%3Dgna&%3Fq=%3Fr", + "name": "U() quoting of ? in args but not base" + }, + + { + "args": ["%% %1%x %1x%x1%xx%11%ff%FF%0f%0F %"], + "exp": "%25%25%20%251%25x%20%251x%25x1%25xx%11%ff%FF%0f%0F%20%25", + "name": "U() quoting of % if not followed by 2 hex digits" + }, + + { + "args": ["path#%% %1%x %1x%x1%xx%11%ff%FF%0f%0F %"], + "exp": "path#%25%25%20%251%25x%20%251x%25x1%25xx%11%ff%FF%0f%0F%20%25", + "name": "U() quoting of % if not followed by 2 hex digits in fragment" + }, + + { + "args": ["a\\b"], + "exp": "a/b", + "name": "U() converting of \\ to /" + }, + + { + "args": ["path/#a\\b"], + "exp": "path/#a%5Cb", + "name": "U() encode \\ in fragment" + }, + + { + "args": ["foo.html?bar=\\gna"], + "exp": "foo.html?bar=%5Cgna", + "name": "U() encode \\ in url parameter" + }, + + { + "args": ["path/#?a=b"], + "exp": "path/#?a=b", + "name": "U() keep query syntax in fragment" + }, + + { + "args": ["path/#Jet d'eau"], + "exp": "path/#Jet%20d%27eau", + "name": "U() with single quotes in fragment" + }, + + { + "args": ["//gna.ch"], + "exp": "//gna.ch/", + "name": "U() add / for absolute url without path" + }, + + { + "args": ["http://gna.ch"], + "exp": "http://gna.ch/", + "name": "U() add / for absolute http url without path" + }, + + { + "args": ["app://settings"], + "exp": "app://settings", + "name": "U() do not add / for non-http url without path" + }, + + { + "args": ["/foo.html", {"": {"": "ham"}}], + "exp": "/foo.html?%3Cspam%3E%5B%3Cspam%3E%5D=ham", + "name": "U() urlencode keys of nested structures" + } +] diff --git a/test/autoprepend.t b/test/autoprepend.t new file mode 100755 index 0000000..bf40605 --- /dev/null +++ b/test/autoprepend.t @@ -0,0 +1,38 @@ +#!/www/server/bin/php -qC +statictext = array( + '_' => array("de" => "Deutsch", "en" => "English"), + 'foo' => array("de" => "bar {v1}", "en" => "qux {v1}"), +); + +is( + T('foo'), + "bar {v1}", + "simple T()" +); + +is( + T('foo', 'en'), + "qux {v1}", + "simple T() with language" +); + +is( + T('foo', array('v1' => "gna "gna "gna 'gna')), "gna\n", "exec with argument"); +is(it::exec("echo {0}", 'gna'), "gna\n", "exec with positional argument"); +is(it::shell_command("echo {arg}", array('arg' => 'gna07,-:blah')), "echo gna07,-:blah", "don't quote arguments with only whitelistes characters"); +is(it::shell_command("echo {arg}", array('arg' => '2>&1')), "echo '2>&1'", "quote arguments with dangerous characters"); +is(it::shell_command("echo {arg}", array('arg' => '')), "echo ''", "quote empty arguments"); + + +is( + it::shell_command("echo {-opts}", array('-opts' => array('--longopt' => true))), + "echo --longopt", + "options argument with long option" +); +is( + it::shell_command("echo {-opts}", array('-opts' => array('-onedash' => true))), + "echo -onedash", + "... with long option but only one dash" +); +is( + it::shell_command("echo {-opts}", array('-opts' => array('-s' => true))), + "echo -s", + "... with short option" +); +is( + it::shell_command("echo {-opts}", array('-opts' => array('--longopt' => 'val'))), + "echo --longopt val", + "... with long option with value" +); +is( + it::shell_command("echo {-opts}", array('-opts' => array('-onedash' => 'val'))), + "echo -onedash val", + "... with long option but only one dash and with value" +); +is( + it::shell_command("echo {-opts}", array('-opts' => array('-s' => 'val'))), + "echo -s val", + "... with short option with value" +); +is( + it::shell_command("echo {-opts}", array('-opts' => array('--longopt' => false))), + "echo ", + "... with disabled long option" +); +is( + it::shell_command("echo {-opts}", array('-opts' => array('-s' => false))), + "echo ", + "... with disabled short option" +); +is( + it::shell_command("echo {-opts}", array('-opts' => array('longopt' => true))), + "echo --longopt", + "... long option without dashes" +); +is( + it::shell_command("echo {-opts}", array('-opts' => array('s' => true))), + "echo -s", + "... short option without dashes" +); + +foreach (array("", "C", "de_CH", "de_CH.utf8") as $locale) { + setlocale(LC_ALL, $locale); + $arg = "preüpost"; + if (it::match('utf8', $locale)) + $arg = utf8_encode($arg); + is(it::exec("echo " . $arg), $arg . "\n", "exec with umlaut (locale '$locale')"); + is(it::exec("echo {arg}", array('arg' => $arg)), $arg . "\n", "exec with argument and umlaut (locale '$locale')"); +} + +is(it::_exec_quotevalue(""), "''", "empty arg needs quotes"); +is(it::_exec_quotevalue("*"), "'*'", "special chars need quotes"); +is(it::_exec_quotevalue("Aabcdef0123456789"), "Aabcdef0123456789", "simple case. tel:debug_getdata needs unquoted vals"); + +is(it::system('exit 0'), 0, 'return exit code 0'); +is(it::system('exit 1'), 1, 'return exit code 1'); +is(it::system('exit -1'), 255, 'return exit code unsigned'); + +@unlink('/tmp/it_system_test'); +it::system('touch /tmp/it_system_test'); +ok(file_exists('/tmp/it_system_test'), 'shell command gets executed'); + +@unlink('/tmp/it_system_test'); +it::system('touch {path}', array('path' => '/tmp/it_system_test')); +ok(file_exists('/tmp/it_system_test'), 'shell command with argument'); + +@unlink('/tmp/it_system_test'); +it::system('touch {0}', '/tmp/it_system_test'); +ok(file_exists('/tmp/it_system_test'), 'shell command with positional argument'); +@unlink('/tmp/it_system_test'); diff --git a/test/getopt.t b/test/getopt.t new file mode 100755 index 0000000..8b0ff70 --- /dev/null +++ b/test/getopt.t @@ -0,0 +1,28 @@ +#!/www/server/bin/php -qC + "blah gnaber", " (umlaute)" => "pre üäpost") as $variant => $testarg) { + getopt_ok(array('-a', $testarg), $testarg, "Short version" . $variant); + getopt_ok(array('--argument', $testarg), $testarg, "Long version with space" . $variant); + getopt_ok(array("--argument=$testarg"), $testarg, "Long version with equal" . $variant); +} + +$_SERVER['argv'] = array('doesnotexist.php', '-0'); +$zero_opts = it::getopt($GLOBALS['usage']); +ok($zero_opts['zero'], '-0'); diff --git a/test/it.t b/test/it.t new file mode 100755 index 0000000..996f44a --- /dev/null +++ b/test/it.t @@ -0,0 +1,474 @@ +#!/www/server/bin/php -qC + true) +); + +match( + 'aBcD', ' aBcD ', + 'aBcD', + "caseinsensitive is default" +); + +match( + '\w+', 'Müller', + 'Müller', + '\w matches umlaut in utf-8 mode' +); + +match( + 'M.ller', 'Müller', + 'Müller', + '. matches umlaut in utf-8 mode' +); + +match( + utf8_decode('ö'), utf8_decode('Ö'), + utf8_decode('Ö'), + 'match umlaute in de_CH.latin1 case insensitive', + array('utf8' => false) +); + +match( + utf8_decode('aöBÜ'), utf8_decode('AÖbü'), + utf8_decode('AÖbü'), + "match umlaute with non-utf-8 override in p", + array('utf8' => false) +); + + +match( + 'abc', "aBc", + null, + "set case sensitivity by parameter", + array('casesensitive' => 1) +); + +match( + '\w+', 'word1 wörd2 word_3', + array('word1', 'wörd2', 'word_3'), + "test all => 1 without captures", + array('all' => 1) +); + +match( + '\w+\s+(\d+)', 'word1 12 wörd2 3 word_3 4', + array('12', '3', '4'), + "test all => 1 with one capture", + array('all' => 1) +); + +match( + '(\w+)\s+(\d+)', 'word1 12 wörd2 3 word_3 4', + array(array('word1', '12'), array('wörd2', '3'), array('word_3', '4')), + "test all => 1 with captures", + array('all' => 1) +); + +match( + '(\w+)\s+(\d+)', 'word1 12 wörd2 3 word_3 4', + array(array('word1', 'wörd2', 'word_3'), array('12', '3', '4')), + "test all => 1,pattern_order => 1", + array('all' => 1, 'pattern_order' => 1) +); + +ini_set('default_charset', 'iso-8859-1'); +match( + 'aöBÜ', "AÖbü", + 'AÖbü', + "match utf-8 umlaute in case insensitive mode with utf8 override", + array('utf8' => true) +); +ini_set('default_charset', 'utf-8'); + + +# +# tests for it::replace() +# +is( + it::replace( + array( + 'regex1' => 'repl1', + 'regex2' => 'repl2', + 'regex3' => 'repl3'), + 'regex2 regex1 regex3'), + 'repl2 repl1 repl3', + 'test tr regex function' +); + +is(it::replace(array('a' => "1", 'b' => "2"), "ab"), "12"); +is(it::replace(array('!' => "x"), "!"), "x"); +is(it::replace(array('\w' => "x"), "oö"), "xx"); +is(it::replace(array('[[:alpha:]]' => "x"), "ö"), "x"); +is(it::replace(array('\w' => "x", '#' => "!"), "#ö"), "!x"); +is(it::replace(array('#' => "!", '\w' => "x"), "#ö"), "!x"); +is(it::replace(array('ö' => "x"), "Ö"), "x"); +is(it::replace(array('a' => "1"), "aaa", array('limit' => 1)), "1aa"); +is(it::replace(array('\s' => "x"), it_html::entity_decode(" ")), "x", "match non-breaking space as white-space character"); +is(it::replace(array('a' => "b", 'b' => "c"), "a"), "c"); + +is(it::grep('ismatch', array('ismatch', 'isnomatch')), array('ismatch'), 'grep with simple regex'); +is(it::grep('!', array('ismatch!', 'isnomatch')), array('ismatch!'), '! in regex'); +is(it::grep('lower|UPPER', array('lower', 'LOWER', 'upper', 'UPPER'), array('casesensitive' => 1)), array(0 => 'lower', 3 => 'UPPER'), 'set casesensitive'); +is(it::grep('match', array('foo' => 'match', 'bar' => 'gna')), array('foo' => 'match'), 'with keys'); +is(it::grep('2', [0 => 1, 1 => 2, 2 => 3], ['invert' => true]), [0 => 1, 2 => 3]); + +setlocale(LC_CTYPE, $oldlocale); +ini_set('default_charset', $oldcharset); # end of tests that must run with specific charset + +# it::cidr_match tests +is(it::cidr_match('192.168.2.3', '192.168.2.5'), false, "cidr_match full IP no match no mask"); +is(it::cidr_match('192.168.2.3', '192.168.2.3'), true, "cidr_match full IP match no mask"); +is(it::cidr_match('192.168.2.3', '192.168.2.5/32'), false, "cidr_match full IP no match"); +is(it::cidr_match('192.168.2.5', '192.168.2.5/32'), true, "cidr_match full IP match"); +is(it::cidr_match('192.168.1.1', '192.168.42.0/24'), false, "cidr_match no match"); +is(it::cidr_match('192.168.42.1', '192.168.42.0/24'), true, "cidr_match basic match"); +is(it::cidr_match('192.168.42.42', '192.168.0.0/16'), true, "cidr_match class b"); +is(it::cidr_match('192.168.42.42', '192.168.42.64/26'), false, "cidr_match offset no match"); +is(it::cidr_match('192.168.42.42', '192.168.42.32/27'), true, "cidr_match offset"); +is(it::cidr_match('2001:918:ff83:101:798e:77c0:b722:fe56', '2001:918:ff83:101::/64'), true, "cidr_match ipv6"); +is(it::cidr_match('2001:918:ff83:102:798e:77c0:b722:fe56', '2001:918:ff83:101::/64'), false, "cidr_match ipv6 no match" ); +is(it::cidr_match('10.11.12.13', array('10.0.0.0/8', '192.168.0.0./16')), true, "cidr_match array"); + +# it::filter_keys tests + +$data = array('a' => 1, 'b' => 2, 'c' => 3); +is(it::filter_keys($data, 'a'), array('a' => 1), "select one key"); +is(it::filter_keys($data, array('a', 'b')), array('a' => 1, 'b' => 2), "select two keys with array"); +is(it::filter_keys($data, 'a,b'), array('a' => 1, 'b' => 2), "select two keys with string"); +is( + array_keys(it::filter_keys($data, 'b,a')), + array('a', 'b'), + "keep order of data array per default"); +is( + array_keys(it::filter_keys($data, 'b,a', array('reorder' => true))), + array('b', 'a'), + "reorder with given key order"); + +# it::date tests + +function _time($debug_time, $expected) +{ + $GLOBALS['debug_time'] = $debug_time; + is(it::date(), $expected, ".time-$debug_time"); + $GLOBALS['debug_time'] = ''; +} +_time("2014-01-01", "2014-01-01 00:00:00"); +_time("2014-01-01-15-27-15", "2014-01-01 15:27:15"); +_time("15-27", it::date('', '15:27')); +_time("1527", it::date('', '15:27')); +_time("15-27-15", it::date('', '15:27:15')); +_time("152715", it::date('', '15:27:15')); +_time("yesterday", it::date('', 'yesterday')); + +is(it::date('date', '2011-10-25'), '25.10.2011', 'parse date string with strtotime'); +is(it::date('date', '2011-10-25 + 3 days'), '28.10.2011', 'some date arithmetic'); +is(it::date('datetime', it::time()), it::date('datetime'), 'recognize int as timestamp'); +is(it::date('datetime', it::time()*1.0), it::date('datetime'), 'recognize float as timestamp'); +is(it::date('datetime', it::time() . ''), it::date('datetime'), 'recognize digit string as timestamp'); +is(it::date('datetime', '@' . it::time()), it::date('datetime'), 'recognize strtotime timestamp format'); +is(it::date('datetime', 10), it::date('datetime', "10"), 'numeric and string give same result'); +is(it::date('datetime', 10.0), it::date('datetime', "10"), '... as long as num is properly truncated'); +is(it::date('datetime', 10.5), it::date('datetime', "10"), '... with one digit after point'); +is(it::date('datetime', 10.56), it::date('datetime', "10"), '... with two digits after point'); +is(it::date('datetime', 1000000), it::date('datetime', "1000000"), '... large nummer'); +is(it::date('datetime', 1000000.543), it::date('datetime', "1000000"), '... large nummer and point'); +is(it::date('time', "10.5"), "10:05", 'interpret string with points with strtotime'); +is(it::date('time', "10.05"), "10:05", 'interpret string with points with strtotime'); + +# it::uc* +is(it::ucfirst('foo bär über'), 'Foo bär über'); +is(it::ucwords('foo bär über'), 'Foo Bär Über'); + +# it::substr_replace +is(it::substr_replace('abcdefgh', 'xyz', 2, 4), substr_replace('abcdefgh', 'xyz', 2, 4), 'it::substr_replace the same as substr_replace for ascii'); +is(it::substr_replace('✔☯♥', '☃☃', 1, 1), '✔☃☃♥', 'it::substr_replace for utf-8'); + +is(grapheme_strlen("\xc1"), null, "need grapheme_strlen side effect for any2utf8"); + +is(it::any2utf8('Meier'), 'Meier', "it::any2utf8 ascii input"); +is(it::any2utf8('Müller'), 'Müller', "it::any2utf8 utf8 input"); +is(it::any2utf8('Aslı'), 'Aslı', "it::any2utf8 utf8 non-latin1 input"); +is(it::any2utf8(utf8_decode('Müller')), 'Müller', "it::any2utf8 latin1 input"); + +is(it::any2utf8( + ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'), # omit soft hyphen cause we filter it + ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ', + "it::any2utf8 utf8 input (exhaustive alphabet)"); +is(it::any2utf8( + utf8_decode(' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ')), + ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ', + "it::any2utf8 latin1 input (exhaustive alphabet)"); + +is(it::any2utf8(utf8_encode("ü")), "ü", "it::any2utf8 double encoding"); + +is(it::any2utf8("Meier"), "Meier", "it::any2utf8 ascii"); +is(it::any2utf8("Müller"), "Müller", "it::any2utf8 utf-8 latin1"); +is(it::any2utf8("Aslı"), "Aslı", "it::any2utf8 utf-8 non-latin1"); +is(it::any2utf8("é»"), "é»", "it::any2utf8 utf-8 latin1 special combination"); +is(it::any2utf8(utf8_encode("Müller")), "Müller", "it::any2utf8 doubly encoded utf8"); +is(it::any2utf8(utf8_encode(utf8_encode("Müller"))), "Müller", "it::any2utf8 triply encoded utf8"); +is(it::any2utf8(utf8_decode("Müller")), "Müller", "it::any2utf8 incorrectly encoded latin1"); +is(it::any2utf8("a💚b"), "a💚b", "it::any2utf8 correctly handles 4-byte utf-8 character GREEN HEART"); + +is(it::any2utf8(array("foo", utf8_decode("bär"))), array("foo", "bär"), "any2utf8 on arrays"); +is(it::any2utf8(array("foo", array(utf8_decode("bär")))), array("foo", array("bär")), "any2utf8 on recursive arrays"); +is(it::any2utf8(array(1, true, false, null)), array(1, true, false, null), "any2utf8 should leave types alone"); +is(it::any2utf8(array(utf8_decode('Müller') => utf8_decode('Müller'))), array('Müller' => 'Müller'), "it::any2utf8 latin1 keys"); + +is(it::any2utf8("\xc2\xad"), "", "it::any2utf8 remove soft hyphens"); + +foreach ([ 'a' => 'ä', 'e' => 'ë', 'i' => 'ï', 'o' => 'ö', 'u' => 'ü' ] as $src => $dst) +{ + is(it::any2utf8("$src\xcc\x88"), $dst, "it::any2utf8 normalize combining diaeresis $dst to umlaut code $dst"); + $src = mb_strtoupper($src); + $dst = mb_strtoupper($dst); + is(it::any2utf8("$src\xcc\x88"), $dst, "it::any2utf8 normalize combining diaeresis $dst to umlaut code $dst"); +} + +foreach (array($dummy, false, true, null, 1, "a", "Ä", "/", array()) as $var) + is(it::json_decode(it::json_encode($var)), $var); + +is(it::sort(array("2!","19!","1!")), array("1!", "19!", "2!")); +is(it::sort(array("2!","19!","1!"), "r"), array("2!", "19!", "1!")); +is(it::sort(array("2!","19!","1!"), "rn"), array("19!", "2!", "1!")); + +is(it::mod(-9, 4), 3); +is(it::mod(-8, 4), 0); +is(it::mod(0, 4), 0); +is(it::mod(7, 4), 3); + +is(it::map('5*$k+$v', array(0 => 1, 1 => 2)), array(1, 7)); +is(it::map(function($k, $v) {return 5*$k+$v;}, array(0 => 1, 1 => 2)), array(1, 7)); +is(it::map('strlen', array("aaa", "aa")), array(3, 2)); +is(it::map('it::ucfirst', array("aaa")), array("Aaa")); +is(it::map('$v->C14N()', DOMDocument::loadXML('42')->childNodes), [ '42' ], "Traversable: Needs copy, not modifiable in-place"); +# Special values which are not callable +is(it::map('null', array("aaa", "aa")), array(null, null)); +is(it::map('1', array("aaa", "aa")), array(1, 1)); +is(it::map(42, array("aaa", "aa")), array(42, 42)); +# Only map selected keys +is(it::map('2*$v', [3, 4, 5], ['keys' => '0,1']), [6, 8, 5]); +is(it::map('2*$v', [3, 4, 5], ['keys' => [0,1]]), [6, 8, 5]); +is(it::map('2*$v', ['foo' => 1, 'bar' => 2], ['keys' => 'foo']), ['foo' => 2, 'bar' => 2]); + +# it::filter +is(it::filter('$v > 2', [1, 5 => 2, 2 => 3]), [2 => 3]); +is(it::filter('$k > 2', [1, 5 => 2, 2 => 3]), [5 => 2]); + +is(it::split("b", "aba"), ["a", "a"]); +is(it::split("b", "aBa"), ["a", "a"]); +is(it::split("b", "abba"), ["a", "", "a"]); +is(it::split("b", "ababa", ['limit' => 2]), ["a", "aba"]); +is(it::split("b", "abbba", ['no_empty' => true]), ["a", "a"]); +is(it::split("(b)", "aba", ['delim_capture' => true]), ["a", "b", "a"]); +is(it::split("b", "aabaa", ['offset_capture' => true]), [["aa", 0], ["aa", 3]]); + +it::file_put_contents("/tmp/it_test", "aa"); +is(($fh = it::fopen("/tmp/it_test", "r")) ? fgets($fh) : null, "aa"); +is(it::file_get_contents("/tmp/it_test"), "aa"); +is(it::file("/tmp/it_test"), ["aa"]); +ob_start(); +it::readfile("/tmp/it_test"); +is(ob_get_clean(), "aa"); + +it::file_put("/tmp/it_test", "bb"); +is(it::file_get("/tmp/it_test"), "bb"); diff --git a/test/it_cache.t b/test/it_cache.t new file mode 100755 index 0000000..8f804d4 --- /dev/null +++ b/test/it_cache.t @@ -0,0 +1,49 @@ +#!/www/server/bin/php + 1)); +it_cache::put('it_cache_d', 0); +unset($GLOBALS['it_cache_local']); +is(intval(it_cache::get('it_cache_d', array('distributed' => 1))), 42, "distributed put/get number"); + +it_cache::put('it_cache_d', false, array('distributed' => 1)); +it_cache::put('it_cache_d', 1); +unset($GLOBALS['it_cache_local']); +is(boolval(it_cache::get('it_cache_d', array('distributed' => 1))), false, "distributed put/get false"); + +it_cache::put('it_cache_d', array(2), array('distributed' => 1)); +it_cache::put('it_cache_d', 0); +unset($GLOBALS['it_cache_local']); +is(it_cache::get('it_cache_d', array('distributed' => 1)), array(2), "distributed put/get array"); + +is(it_cache::get('it_cache_d'.rand(1, 1000), array('distributed' => 1)), null, "distributed get unknown key"); diff --git a/test/it_dbi.t b/test/it_dbi.t new file mode 100755 index 0000000..6a37637 --- /dev/null +++ b/test/it_dbi.t @@ -0,0 +1,364 @@ +#!/www/server/bin/php -qC + "lib_search_ch", 'safety' => 0); +$dbi = new it_dbi($db); +$dbi->query('create temporary table it_dbi_test ( + ID int not null auto_increment, + x int, + foo varchar(42), + dyncols LONGBLOB, + primary key(ID) +);'); + +$record = new it_dbi($db + array('table' => "it_dbi_test")); + +$record->insert(array('x' => 42, 'foo' => null)); +$record->insert(array('foo' => "bar")); +$record->insert(array('x' => 64738, 'foo' => "q'uux")); + +is( + $record->ID, + 3, + "auto_increment" +); + +$record->read(1); +is( + array($record->_key, $record->x, $record->foo), + array(1, 42, null), + "read" +); + +is( + $record->select(), + 3, + "select without parameters select all" +); + +is( + $record->select(array('foo <>' => ""), "LIMIT 1"), + 1, + "select with multiple parameters (LIMIT part)" +); +is( + $record->foo, + "bar", + "select with multiple parameters (foo part)" +); +is( + $record->select(array('ID IN' => array(2,3))), + 2, + "select with IN" +); +is( + $record->select(array('ID NI' => array(2,3))), + 2, + "select with NI" +); +is( + $record->select(array('ID NOT IN' => array(2,3))), + 1, + "select with NOT IN" +); +is( + $record->select(array('ID IN' => array())), + 0, + "select with empty IN" +); +is( + $record->select(array('ID NOT IN' => array())), + 3, + "select with empty NOT IN" +); +is( + $record->select(array('ID' => array(2,3))), + 2, + "select with implicit IN" +); + +it_dbi::createclass(array('table' => "it_dbi_test", 'forcecreate' => true)); + +$record = new it_dbi_test; +is( + $record->x, + null, + "constructor of created class without argument" +); + +$record = new it_dbi_test(2); +is( + $record->ID, + 2, + "constructor of created class with id parameter" +); + +$record = new it_dbi_test(array('x >' => 0), "ORDER BY x DESC"); +is( + $record->x, + 64738, + "constructor of created class with multiple select parameters" +); + +$record = new it_dbi_test(array('foo' => 'bar')); +is( + $record->ID, + 2, + "constructor of created class with single array parameter" +); + +$record = new it_dbi($db + array('table' => "it_dbi_test")); +is( + $record->x, + null, + "constructor without parameters" +); + +$record = new it_dbi($db + array('table' => "it_dbi_test"), array('x >' => 0), "ORDER BY x DESC"); +is( + $record->x, + 64738, + "constructor with multiple select parameters" +); + +$record->select(array('x' => 64738)); +is( + array($record->_key, $record->x, $record->foo), + array(3, 64738, "q'uux"), + "select" +); + +$record->update(array('x' => 17)); +is( + array($record->_key, $record->x, $record->foo), + array(3, 17, "q'uux"), + "update" +); + +is( + $record->update(array('x' => 18), array('x' => 17)), + 1, + "return affected rows" +); + +is( + $record->update(array('x' => 18), array('x' => 17)), + 0, + "return zero affected rows" +); + +$record->update(array('-x' => 'POW(2,2) * 10')); +is( + array($record->_key, $record->x, $record->foo), + array(3, 40, "q'uux"), + "update with function" +); + +$record->update(array('foo' => "00")); +$rand = $record->x; +is ( + $record->_set(array('x' => $rand, 'foo' => "0")), + "SET `foo`='0'", + 'update: _set optimization' +); + +$record->update(array('foo' => NULL)); +is ( + $record->_set(array('foo' => "")), + "SET `foo`=''", + 'update: _set optimization with NULL => ""' +); +$record->update(array('foo' => "bar")); +$record->update(array('foo' => "")); +is ( + $record->_set(array('foo' => NULL)), + "SET `foo`=NULL", + 'update: _set optimization with "" => NULL' +); + +$record->update(array('foo' => "bar")); +$record->select(array('foo' => "bar")); +$record->iterate(); +is( + array($record->_key, $record->x, $record->foo), + array(2, null, "bar"), + "iterate record 2" +); +$record->update(array('foo' => "qux")); +$record->iterate(); +is( + array($record->_key, $record->x, $record->foo), + array(3, $rand, "bar"), + "iterate record 3" +); +$record->update(array('foo' => "quux")); + +$record->read(2); +is( + array($record->_key, isset($record->x), $record->foo), + array(2, false, "qux"), + "iterate update record 2" +); + +$record->read(3); +is( + array($record->_key, $record->x, $record->foo), + array(3, $rand, "quux"), + "iterate update record 3" +); + +is( + gettype($record->_key) . "/" . gettype($record->x) . "/" . gettype($record->_data['x']) . "/" . gettype($record->foo), + "integer/integer/integer/string", + "automatic type detection" +); + +$count = 0; +foreach (new it_dbi_test as $id => $record) +{ + $count++; + is($record->_key, $id, "Iterator id $id"); +} +is($count, 3, "Iterator without select"); + +$count = 0; +foreach (new it_dbi_test(array('foo <>' => "")) as $id => $record) +{ + $count++; + is($record->_key, $id, "Iterator id $id"); +} +is($count, 2, "Iterator with select"); + +$count = 0; +foreach ($record as $dummy_rec) + $count++; +is($count, 2, "Iterator reused"); + +$GLOBALS['debug_sqllog'] = true; +@$record->store(array('ID' => 5, 'x' => 6)); +like( + $record->_sqllog[1]['query'], + "REPLACE", + "store => REPLACE for new entries" +); +$record->clear(); +@$record->read(5); +is( + $record->x, + 6, + "saving with store" +); + +$record->_sqllog = array(); +@$record->store(array('ID' => 5, 'x' => 7)); +like( + $record->_sqllog[1]['query'], + "UPDATE", + "store => UPDATE for existing entries" +); +$record->clear(); +@$record->read(5); +is( + $record->x, + 7, + "updating with store" +); + +$record->_sqllog = array(); +@$record->store(array('ID' => 5, 'x' => 7)); +is( + $record->_sqllog[1]['query'], + null, # Only SELECT, no UPDATE + "Optimized away UPDATE with same values" +); +$GLOBALS['debug_sqllog'] = false; + +# test latin1 (produces warnings on stderr for failing) + +$record = new it_dbi(array('table' => 'it_dbi_test', 'charset' => 'latin1')); +$record->select(array('foo' => "\xc3\x28")); + +# Test field localization feature + +$dbi->query('create temporary table it_dbi_testlocalized ( + ID int not null auto_increment, + foobar_de varchar(42), + foobar_fr varchar(42), + primary key(ID) +);'); + +$record = new it_dbi($db + array('table' => "it_dbi_testlocalized")); +$record->insert(array('foobar_de' => "deutsch", 'foobar_fr' => "franz")); +$record->insert(array('foobar_de' => "deutsch2", 'foobar_fr' => "franz2")); + +T_set_language('de'); +$record->select(array()); +$record->iterate(); +is( + array($record->_key, $record->foobar), + array(1, "deutsch"), + "localized field foobar_de" +); +$record->iterate(); +is( + array($record->_key, $record->foobar), + array(2, "deutsch2"), + "localized field foobar_de iterate" +); + +T_set_language('fr'); +$record->read(1); +is( + array($record->_key, $record->foobar), + array(1, "franz"), + "localized field foobar_fr" +); + +# +# Test saving/retrieving/matching values in dynamically created columns +# +$record = new it_dbi_test; +$record->replace(['ID' => 5, 'key1' => "val1"]); is($record->key1, "val1"); +$record->update(['key2' => "val2"]); is($record->key1, "val1"); is($record->key2, "val2"); +$record->update(['-key3' => "2*2"]); is($record->key1, "val1"); is($record->key2, "val2"); is($record->key3, 4); +$record->update(['key1' => "val0"]); is($record->key1, "val0"); is($record->key2, "val2"); is($record->key3, 4); +$record->replace(['ID' => 6, 'key4' => "val4"]); is($record->key4, "val4"); +$record->select(['key2' => "val2"]); is($record->key2, "val2"); is($record->key4, null, "clear previous fields"); + +# +# Test tracked update +# +function allrecs() +{ + foreach (new it_dbi_test([0 => "ORDER BY ID"]) as $r) + $result[] = ['ID' => $r->ID, 'foo' => $r->foo]; + return json_encode($result); +} + +$record = new it_dbi_test; +$record->delete(['WHERE 1' ]); +$record->upsert(['ID' => 1, 'foo' => "a"]); +$record->upsert(['ID' => 2, 'foo' => "b"]); +$record->upsert(['ID' => 2, 'foo' => "B"]); +is($record->delete_untouched(), 0, 'delete_untouched'); +is(allrecs(), '[{"ID":1,"foo":"a"},{"ID":2,"foo":"B"}]', 'data after delete_untouched'); + +$record->upsert(['ID' => 1, 'foo' => "A"]); +$record->upsert(['ID' => 3, 'foo' => "c"]); +is($record->delete_untouched([ 'ID >' => 2 ]), 0, 'delete_untouched with query'); +is(allrecs(), '[{"ID":1,"foo":"A"},{"ID":2,"foo":"B"},{"ID":3,"foo":"c"}]', 'data after delete_untouched with query'); + +$record->upsert(['ID' => 3, 'foo' => "C"]); +is($record->delete_untouched(), 2, 'delete_untouched with query'); +is(allrecs(), '[{"ID":3,"foo":"C"}]', 'data after delete_untouched with query'); + +$record->replace(['ID' => 4, 'foo' => "C"]); +is($record->delete_untouched(), 1, 'delete_untouched after replace'); +is(allrecs(), '[{"ID":4,"foo":"C"}]', 'data after delete_untouched after replace'); + +$record->upsert(['ID' => 4, 'foo' => "C"]); +is($record->delete_untouched(), 0, 'delete_untouched after upsert without changes'); +is(allrecs(), '[{"ID":4,"foo":"C"}]', 'data after delete_untouched after upsert without changes'); diff --git a/test/it_html.t b/test/it_html.t new file mode 100755 index 0000000..5e19b74 --- /dev/null +++ b/test/it_html.t @@ -0,0 +1,291 @@ +#!/www/server/bin/php -qC + "html5", 'prettyprint' => false, 'error_on_redefine' => false)); + +is( + a(array('href' => "&foo", 'true' => true, 'false' => false, 'null' => null, 'empty' => ""), "bar"), + 'bar', + "tag with attributes" +); + +is( + div(), + "
\n", + "empty div tag" +); + +is( + h1(), + "

\n", + "empty h1 tag" +); + +is( + input(), + "", + "no closing tag for void elements" +); + +is( + img(array('src' => "foo.png", 'alt' => "ALT")), + 'ALT', + "img tag with attributes" +); + +is( + tag('link'), + "\n", + "empty link tag" +); + +is( + tag('link', "foo"), + "foo\n", + "link tag with data" +); + +is( + it::replace(array('\n+\s*' => ""), select(array('name' => "gna", 'multiple' => true), '1:foo,2:bar', '1,2')), + '', + "select tag with multiselect" +); + +is( + it::replace(array('\n+\s*' => ""), select(array('name' => "gna"), array("1" => "foo", "2" => 'bar', '1,2' => "qux"), '1,2')), + '', + "select tag without multiselect" +); + +is( + div(array('arg' => "val: \x03, \x0e, \x0f, \x0c, \xc2\x80, \xc2\x9f, \t, \n, \r", "\x02, \x0e, \x0f, \x0c, \xc2\x80, \xc2\x9f, \t, \n, \r")), + "
\x02, \x0e, \x0f, \x0c, \xc2\x80, \xc2\x9f, \t, \n, \r
\n", + "blank unprintable characters and illegal utf8 in attributes but not in normal text" +); + +is( + div(array("arg\x03\x0e\x0f\xc2\x80\xc2\x9fendarg" => "value", "content")), + "
content
\n", + "don't blank unprintable characters and illegal utf8 in attribute names" +); + +is( + div(array('arg' => "abc äüö éá© œàè îôÇ xyz", "abc äüö éá© œàè îôÇ xyz")), + "
abc äüö éá© œàè îôÇ xyz
\n", + "leave legal utf8 intact" +); + +unset($GLOBALS['debug_utf8check']); +is( + div(array('arg' => "value \xc2", "content")), + "
content
\n", + "handle single \\xc2 at end of attribute value" +); + +is( + div(array("arg\x00end" => "value \x00 end", "content \x00 content end")), + "
content \x00 content end
\n", + "handle 0-bytes" +); + +is( + div(array('arg' => "& \" < > \n '", "& \" < > \n '")), + "
& \" < > \n '
\n", + "use html entities in attributes but not in normal text" +); + +is( + html(), + "\n\n\n", + "html5 doctype" +); + +# Test different html types +foreach (array('html5' => "
", 'html' => "
", 'xhtml' => "
", 'xhtml-mobile' => "
") as $type => $value) +{ + unset($GLOBALS['it_html']); + new it_html(array('htmltype' => $type, 'error_on_redefine' => false)); + is (trim(br(array('flag' => true))), $value, "Check empty tag and attribute for $type"); +} + +# XHTML generation +unset($GLOBALS['it_html']); +new it_html(array('htmltype' => "xhtml", 'tags' => "script", 'error_on_redefine' => false)); + +is( + script(), + "\n", + "script may not be shortened (see xhtml spec)" +); + +is( + h1(), + "

\n", + "empty h1 tag in xhtml context" +); + +is( + html(), + "\n\n\n", + "xhtml doctype" +); + + +# XML generation +unset($GLOBALS['it_html']); +new it_html(array('htmltype' => "xml", 'name' => 'it_html', 'tags' => "xmltest", 'error_on_redefine' => false)); + +is( + xmltest(), + "\n", + "empty xmltest tag" +); + +is( + xmltest("foo"), + "foo\n", + "non-empty xmltest tag" +); + +is( + xmltest(array('href' => "&foo", 'true' => true, 'false' => false, 'null' => null, 'empty' => "")), + '' . "\n", + "xmltest tag with attributes" +); + +is( + h1(), + "

\n", + "empty h1 tag in xml context" +); + +is( + input(), + "", + "empty input tag in xml context" +); + +# Inheriting and extending it_html +class myhtml extends it_html +{ + +function myhtml($p = array()) +{ + parent::__construct($p + ['moretags' => 'overriddentag,defaulttag', 'nonewlinetags' => 'a,b,defaulttag,em,img,input,overriddentag,span,div']); +} + +function myimg($args) +{ + array_unshift($args, array('alt' => "ALT", 'bar' => "BAR")); + return parent::img(array_filter(it_parse_args($args))); +} + +function overriddentag($args) +{ + return parent::overriddentag($args); +} + +} + +unset($GLOBALS['it_html']); +new myhtml(array('htmltype' => "html", 'error_on_redefine' => false)); + +is( + myimg(['src' => "foo.gif", 'alt' => "foo"]), + 'foo', + "it_html inheritance" +); + +is( + div(array('attr' => 'value'), 'content'), + '
content
', + "different nonewlinetags respected" +); + +is( + overriddentag("one ", ['src' => "evil", 'alt' => ""], "two ", ['foo' => "bar"], "three"), + 'one two three', + "moretags override" +); + +is( + defaulttag("one ", ['src' => "evil", 'alt' => ""], "two ", ['foo' => "bar"], "three"), + 'one two three', + "moretags default" +); + +is( + it_html::sanitize(" \r \n " . '

swisspics posted < < ä & yesterday a photo tag missmatch:



+ +

Wolken

+'), + ' swisspics posted < < ä & yesterday a photo tag missmatch:

', + 'it_html::sanitize tag soup' +); + +is( + it_html::sanitize('q←x'), + "q←x", + 'it_html::sanitize preserve numeric entities' +); + +is( + it_html::sanitize('qüx'), + "q\xc3\xbcx", + 'it_html::sanitize with utf-8' +); + +is( + it_html::sanitize('a
b
'), + "a
b
", + 'it_html::sanitize with b and br (tag prefix of other tag bug)' +); + +is( + it_html::sanitize('
'), + '', + 'empty tags removal' +); + +foreach (json_decode(file_get_contents(dirname($argv[0]) . '/U_tests.json'), true) as $test) { + is(U(...$test['args']), $test['exp'], $test['name']); +} + +is(it_html::entity_decode("ä"), "ä"); +is(it_html::entity_decode("J"), "J"); +is(it_html::entity_decode("J"), "J"); +is(it_html::entity_decode("A"), "A"); + +# tests for itools extension +is(table(null), "
\n", "table() null argument"); +list($data, $attr) = it_parse_args(array(null)); +ok($data === "", "it_parse_args compatiblity: treat null like empty string"); + +# +# check transliterations in iso-8859-1 +# + +it_html::configure(array('charset' => "iso-8859-1")); +ini_set('default_charset', "iso-8859-1"); + +is( + it_html::sanitize('qüx'), + "q\xfcx", + 'it_html::sanitize with latin1' +); + +is( + it_html::sanitize('q←x'), + "q←x", + 'it_html::sanitize preserve non-decodable numeric entities' +); +is(it_html::entity_decode("’"), "'", "it_html::entity_decode numeric decimal entity"); +is(it_html::entity_decode("࿿"), " ", "it_html::entity_decode invalid numeric hex entity"); +is(it_html::entity_decode("ϧ"), " ", "it_html::entity_decode invalid numeric decimal entity"); +is(it_html::entity_decode("‹"), " ", "it_html::entity_decode entity von 0x80-0x9f"); diff --git a/test/it_mail.t b/test/it_mail.t new file mode 100755 index 0000000..486dee8 --- /dev/null +++ b/test/it_mail.t @@ -0,0 +1,93 @@ +#!/www/server/bin/php -qC +addrlist_escape('éxample@example.com', true), + 'éxample@example.com', + "Don't escape plain email addresses in email headers" +); + +is( + $mail->header_escape('éxample@example.com'), + '=?utf-8?Q?=C3=A9xample@example.com?=', + "Escape plain email in non-email headers" +); + +is( + $mail->header_escape('search.ch e-mail code d\'accès'), + '=?utf-8?B?c2VhcmNoLmNoIGUtbWFpbCBjb2RlIGQnYWNjw6hz?=', + "Use base64 encoding when php iconv fails with quoted-printable (workaround for php bug #53891)" +); + +is( + $mail->addrlist_escape('Èxample User <èxample@example.com>', true), + '=?utf-8?Q?=C3=88xample=20User?= <èxample@example.com>', + "Escape name but not email in email headers" +); + +is( + $mail->addrlist_escape('Example User ', true), + 'Example User ', + "Don't escape characters that don't need escaping" +); + +is( + $mail->addrlist_escape('example@example.com, éxample@example.com, Sömeone Ëlse ', true), + 'example@example.com, éxample@example.com, =?utf-8?Q?S=C3=B6meone=20=C3=8Blse?= ', + "Don't escape email addresses but escape realnames" +); + +is( + $mail->addrlist_escape('"Alfred E. Neuman" ', true), + '"Alfred E. Neuman" ', + "Don't remove quoting characters from realname" +); + +is( + $mail->addrlist_escape('"Schmitt, Sören" ', true), + '=?utf-8?Q?"Schmitt,=20S=C3=B6ren"?= ', + "Don't remove quoting characters from realname when it contains a quotable character" +); + +$mail = new it_mail(array( + 'From' => 'Someone Ïmportant <ïmportant@search.ch>', + 'To' => 'éxample@example.com, example@example.com, Sömeone Ëlse , "Alfred E. Neuman" , "Schmitt, Sören" ', + 'Cc' => 'éxample@example.com, example@example.com, Sömeone Ëlse , "Alfred E. Neuman" , "Schmitt, Sören" ', + 'Bcc' => 'éxample@example.com, example@example.com, Sömeone Ëlse , "Alfred E. Neuman" , "Schmitt, Sören" ', + 'Subject' => "§önÐë®z€ı¢ħèṇ" +)); + +is( + $mail->to[0], + 'éxample@example.com, example@example.com, =?utf-8?Q?S=C3=B6meone=20=C3=8Blse?= , "Alfred E. Neuman" , =?utf-8?Q?"Schmitt,=20S=C3=B6ren"?= ', + 'Escape To: field as addrlist' +); + +is( + $mail->cc[0], + 'éxample@example.com, example@example.com, =?utf-8?Q?S=C3=B6meone=20=C3=8Blse?= , "Alfred E. Neuman" , =?utf-8?Q?"Schmitt,=20S=C3=B6ren"?= ', + 'Escape Cc: field as addrlist' +); + +is( + $mail->bcc[0], + 'éxample@example.com, example@example.com, =?utf-8?Q?S=C3=B6meone=20=C3=8Blse?= , "Alfred E. Neuman" , =?utf-8?Q?"Schmitt,=20S=C3=B6ren"?= ', + 'Escape Bcc: field as addrlist' +); + +is( + $mail->header_values[0], + '=?utf-8?Q?Someone=20=C3=8Fmportant?= <ïmportant@search.ch>', + 'Escape From: field as addrlist' +); + +is( + $mail->subject, + "§önÐë®z€ı¢ħèṇ", + "Don't escape Subject: field on instanziation" +); diff --git a/test/it_pipe.t b/test/it_pipe.t new file mode 100755 index 0000000..16e2c29 --- /dev/null +++ b/test/it_pipe.t @@ -0,0 +1,18 @@ +#!/www/server/bin/php + ["a\tb", "1\t2"]]))->csv()), '[{"a":"1","b":"2"}]'); +is(json_encode((new it_pipe(['data' => ["a;b", "1;2"]]))->csv()), '[{"a":"1","b":"2"}]'); +is(json_encode((new it_pipe(['data' => ["a,b", "1,2"]]))->csv()), '[{"a":"1","b":"2"}]'); +is(json_encode((new it_pipe(['data' => ["\t", "1\t2"]]))->csv()), '[{"field0":"1","field1":"2"}]'); +is(json_encode((new it_pipe(['data' => ["a\tb", "1\t2"]]))->csv("c,d")), '[{"c":"1","d":"2"}]'); +is(json_encode((new it_pipe(['data' => ["a\tb", "1\t2"]]))->csv(['forceschema' => "c,d"])), '[{"c":"1","d":"2"}]'); +is(json_encode((new it_pipe(['data' => ["a b\tb", "1\t2"]]))->csv(['fixcolnames' => true])), '[{"a_b":"1","b":"2"}]'); + +# askey() +is(json_encode((new it_pipe(['data' => "a\nb\n"]))->askey()), '{"a":true,"b":true}'); + +# __call() generic funcs +is((new it_pipe(['data' => [" a a "]]))->trim()->lines[0], "a a"); +is((new it_pipe(['data' => [" a a "]]))->it__match('\w', ['all' => true])->lines[0], ["a", "a"], "test argument in second pos"); diff --git a/test/it_text.t b/test/it_text.t new file mode 100755 index 0000000..5629d51 --- /dev/null +++ b/test/it_text.t @@ -0,0 +1,21 @@ +#!/www/server/bin/php +x = "attr"; +$obj->y->z = "attr"; + +is(it_text::transmogrify(""), ""); +is(it_text::transmogrify("foo"), "foo"); +is(it_text::transmogrify("{foo}"), ""); +is(it_text::transmogrify("{foo}", array('foo' => 1)), "1"); +is(it_text::transmogrify("{foo}{foo}", array('foo' => 1)), "11"); +is(it_text::transmogrify("{foo}X", array('foo' => 1)), "1X"); +is(it_text::transmogrify("{foo}X{foo}", array('foo' => 1)), "1X1"); +is(it_text::transmogrify("X{foo}", array('foo' => 1)), "X1"); +is(it_text::transmogrify("X{foo}{bar}", array('foo' => 1, 'bar' => 2)), "X12"); +is(it_text::transmogrify("X{foo}", array('foo' => "&")), "X&"); +is(it_text::transmogrify("X{x}", $obj), "Xattr"); +is(it_text::transmogrify("X{y.z}", $obj), "Xattr"); +is(it_text::transmogrify("{mb_strlen(xxx)}", null, null, ['mb_strlen' => 1]), "3"); +is(it_text::transmogrify("{it::ucfirst(xxx)}", null, null, ['it::ucfirst' => 1]), "Xxx"); +is(it_text::transmogrify("{format violation}"), '{format violation}'); diff --git a/test/it_url.t b/test/it_url.t new file mode 100755 index 0000000..eb36dd6 --- /dev/null +++ b/test/it_url.t @@ -0,0 +1,315 @@ +#!/www/server/bin/php -qC +url, + 'http://www.relog.ch/default.asp', + '$url->url' +); + +is( + $url->protocol, + 'http', + '$url->protocol' +); + +is( + $url->hostname, + 'relog.ch', + '$url->hostname' +); + +is( + $url->realhostname, + 'www.relog.ch', + '$url->realhostname' +); + +is( + $url->port, + 80, + '$url->port' +); + +is( + $url->path, + 'default.asp', + '$url->path' +); + +is( + $url->user, + 'falcon', + '$url->user' +); + +is( + $url->pass, + 'joshua', + '$url->pass' +); + +# and now check for path +$url = new it_url('HTTP://falcon:joshua@www.Relog.CH:80/foo/bar.html'); + +is( + $url->path, + 'foo/bar.html', + '$url->path' +); + +# punycode test +$url = new it_url('http://www.sörtsch.ch/?q=ültra#sörtsch'); +is( + $url->realhostname, + 'www.xn--srtsch-wxa.ch', + 'punycode $url->realhostname' +); + +# it_url::absolute() tests +list ($_SERVER['HTTP_HOST'], $_SERVER['SERVER_PORT'], $_SERVER['HTTPS']) = ["gna.ch", null, null]; +is( + it_url::absolute("/"), + 'http://gna.ch/', + 'it_url::absolute basic' +); + +list ($_SERVER['HTTP_HOST'], $_SERVER['SERVER_PORT'], $_SERVER['HTTPS']) = ["gna.ch:42", 42, null]; +is( + it_url::absolute("/port"), + 'http://gna.ch:42/port', + 'it_url::absolute with non-standard port' +); + +list ($_SERVER['HTTP_HOST'], $_SERVER['SERVER_PORT'], $_SERVER['HTTPS']) = ["gna.ch", 443, true]; +is( + it_url::absolute("/https"), + 'https://gna.ch/https', + 'it_url::absolute for https' +); + +list ($_SERVER['HTTP_HOST'], $_SERVER['SERVER_PORT'], $_SERVER['HTTPS']) = ["gna.ch", null, null]; +is( + it_url::absolute("/https", "https"), + 'https://gna.ch/https', + 'it_url::absolute force https in http context' +); + +list ($_SERVER['HTTP_HOST'], $_SERVER['SERVER_PORT'], $_SERVER['HTTPS']) = ["gna.ch", 443, true]; +is( + it_url::absolute("/foo", "http"), + 'http://gna.ch/foo', + 'it_url::absolute force http in https context' +); + +list ($_SERVER['HTTP_HOST'], $_SERVER['SERVER_PORT'], $_SERVER['HTTPS']) = ["gna.ch", null, null]; +is( + it_url::absolute("http://gna.ch/foo", "https"), + 'https://gna.ch/foo', + 'it_url::absolute force https overwriting existing url' +); + + +$url = new it_url('http://www.gna.ch/'); +$page = $url->get(); +ok( + strpos($page, ''), # UTF8SAFE + '$url->get with url in constructor' +); + +$url = new it_url('http://bogus.url'); +$page = $url->get('http://www.gna.ch/'); +ok( + strpos($page, ''), # UTF8SAFE + '$url->get(url) with url as string arg' +); + +$url = new it_url('http://bogus.url'); +$page = $url->get(array('url' => 'http://www.gna.ch/')); +ok( + strpos($page, ''), # UTF8SAFE + '$url->get(\'url\' => url) with url as named arg' +); +is( + $url->result, + '200', + '$url->result = 200' +); +is( + $url->headers['Server'], + 'Apache', + '$url->headers correctly set' +); + +unset($url, $page); +$page = it_url::get('http://www.gna.ch/'); +ok( + strpos($page, ''), # UTF8SAFE + 'it_url::get() static call' +); + +require 'it_url_server.php'; + +handle_server( + is( + it_url::get('http://localhost:8000/'), + "Testserver root output", + 'it_url::get() static call with port' + ) +); + +handle_server( + is( + it_url::get('//localhost:8000/'), + "Testserver root output", + 'it_url::get() static call with port but no protocol' + ) +); + +handle_server( + is( + it_url::get(['url' => 'http://localhost:8000/', 'maxlength' => 100]), + "Testserver root output", + 'it_url::get() static call with port and maxlength' + ) +); + +handle_server( + is( + it_url::get(['url' => 'http://localhost:8000/', 'maxlength' => 5, 'it_error' => false]), + false, + 'it_url::get() static call with port and too small maxlength' + ) +); + +handle_server( + is( + it_url::get('http://localhost:8000/temp_redirect'), + "Testserver output after temporary redirect", + 'it_url::get() follows temproary redirect' + ) +); + +handle_server( + is( + it_url::get('http://localhost:8000/perm_redirect'), + "Testserver output after permanent redirect", + 'it_url::get() follows permanent redirect' + ) +); + +handle_server( + is( + it_url::get(['url' => 'http://localhost:8000/relative_redirect', 'it_error' => false]), + "Testserver output after relative redirect", + 'it_url::get() follows relative redirect correctly' + ) +); + +handle_server( + is( + it_url::get('http://localhost:8000/nohost_redirect'), + "Testserver output after nohost redirect", + 'it_url::get() follows redirect without host correctly' + ) +); + +handle_server( + is( + it_url::get('http://localhost:8000/created_redirect'), + "Testserver output *before* created redirect", + 'it_url::get() does not follow Location of 201 (Created) result' + ) +); + +$output = handle_server( + ok( + !it_url::get(array('url' => U('http://localhost:8000/redirect_loop', array('num' => 40)), 'it_error' => false)), + 'it_url::get() handles redirect loop' + ) +); +$last_num = it::match('num=(\d+)', end($output)); +if (!ok( + $last_num == 20, + 'it_url::get() aborts redirect loop after 5 redirects' +)) + diag($output); + +$output = handle_server( + ok( + !it_url::get(array('url' => 'http://localhost:8000/does_not_exist', 'retries' => 4)), + 'it_url::get() on 404' + ) +); +if (!ok( + count(preg_grep('/^Got Request:/', $output)) == 1, + 'it_url::get() does not retry on 404' +)) + diag($output); + +$output = handle_server( + ok( + !it_url::get(array('url' => 'http://localhost:8000/repeat?num=0', 'retries' => 4)), + 'it_url::get() on empty page' + ) +); +if (!ok( + count(preg_grep('/^Got Request:/', $output)) == 5, + 'it_url::get() does retry on empty page' +)) + diag($output); + +handle_server( + is( + it_url::get(U('http://localhost:8000/get_server_value', array('key' => 'HTTP_HOST'))), + 'localhost:8000', + 'it_url::get() sets correct Host header' + ) +); + +handle_server( + is( + it_url::get(U('http://localhost:8000/get_server_value', array('key' => 'HTTP_ACCEPT_LANGUAGE'))), + T_lang(), + 'it_url::get() sets correct Accept-Language header' + ) +); + +handle_server( + ok( + it::match('Mozilla', it_url::get(U('http://localhost:8000/get_server_value', array('key' => 'HTTP_USER_AGENT')))), + 'it_url::get() sets User-Agent containing Mozilla' + ) +); + +handle_server( + is( + it_url::get(U('http://user:password@localhost:8000/get_server_value', array('key' => 'PHP_AUTH_USER'))), + 'user', + 'it_url::get() basic authentication user' + ) +); + +handle_server( + is( + it_url::get(U('http://user:password@localhost:8000/get_server_value', array('key' => 'PHP_AUTH_PW'))), + 'password', + 'it_url::get() basic authentication password' + ) +); + +$pages = it_url::get_multi(array('urls' => array('a' => 'http://www.gna.ch/', 'b' => 'http://search.ch/'))); +ok(strpos($pages['a'], ''), 'it_url::get_multi got first url'); # UTF8SAFE +ok(strpos($pages['b'], ''), 'it_url::get_multi got second url'); # UTF8SAFE +is(count($pages), 2, 'it_url::get_multi no additional array elements'); + +is(it_url::parse("/foo"), array("/foo"), "it_url::parse path only"); +is(it_url::parse("/foo?"), array("/foo"), "it_url::parse empty parameter"); +is(it_url::parse("/foo?bar=baz&qux=quux"), array("/foo", 'bar' => "baz", 'qux' => "quux"), "it_url::parse parameters"); diff --git a/test/it_url.testserver.php b/test/it_url.testserver.php new file mode 100644 index 0000000..530a505 --- /dev/null +++ b/test/it_url.testserver.php @@ -0,0 +1,71 @@ + 0) + header("Location: $base/redirect_loop?num=" . ($_REQUEST['num'] - 1), true, 303); + break; + + case "/nohost_redirect": + header('Location: /redirect_target?type=nohost', true, 301); + exit; + + case "/relative_redirect": + header('Location: ../redirect_target?type=relative', true, 301); + exit; + + case "/redirect_target": + echo 'Testserver output after ' . $_REQUEST['type'] . ' redirect'; + break; + + case "/long_sleep": + sleep(6); + echo 'Testserver output after long sleep'; + break; + + case "/short_sleep": + sleep(4); + echo 'Testserver output after short sleep'; + break; + + case "/slow_response": + for ($i = 0; $i < 6; $i++) { + sleep(1); + echo "Testserver slow output $i\n"; + } + break; + + case "/get_server_value": + echo $_SERVER[$_REQUEST['key']]; + break; + + case "/repeat": + for ($i = 0; $i < $_REQUEST['num']; $i++) + echo $_REQUEST['string']; + break; + + default: + http_response_code(404); + fwrite($stderr, "Unknown path '$_SERVER[PHP_SELF]' not handled!\n"); +} diff --git a/test/it_url_server.php b/test/it_url_server.php new file mode 100644 index 0000000..3805a79 --- /dev/null +++ b/test/it_url_server.php @@ -0,0 +1,32 @@ + fopen('/dev/null', 'r'), 1 => fopen('/dev/null', 'w'), 2 => array('pipe', 'w')), + $pipes +); +register_shutdown_function( + function ($server) { + proc_terminate($server); + }, + $server +); + +usleep(100000); +stream_set_blocking($pipes[2], 0); + +function server_output() { + $result = array(); + while (($result[] = trim(fgets($GLOBALS['pipes'][2])))) {}; + return array_filter($result); +} + +function handle_server($args) { + $res = true; + foreach ((array)$args as $arg) + $res = $res && $arg; + $output = server_output(); + if (!$res) + diag($output); + else + return $output; +} diff --git a/test/it_url_slow.t b/test/it_url_slow.t new file mode 100755 index 0000000..32f0963 --- /dev/null +++ b/test/it_url_slow.t @@ -0,0 +1,59 @@ +#!/www/server/bin/php -qC +