/** * Output test results formatted for CLI or web * @param Test $test */ function showResults(Test $test) { $err = false; $f3 = Base::instance(); if (PHP_SAPI == 'cli') { // Command line foreach ($test->results() as $result) { if ($result['status']) { if (defined('PHP_WINDOWS_VERSION_MAJOR')) { echo "PASS"; } else { echo "[0;32m", 'PASS', "[0m"; } echo ": ", $result['text'], "\n"; } else { if (defined('PHP_WINDOWS_VERSION_MAJOR')) { echo "FAIL"; } else { echo "[0;31m", 'FAIL', "[0m"; } echo ": ", $result['text'], " - ", $result['source'], "\n"; $err = true; } } if ($err) { echo "One or more tests failed. Last error:\n"; echo $f3->get("ERROR.text"), " at " . $f3->get("ERROR.trace.0.file"), ":", $f3->get("ERROR.trace.0.line"), "\n"; register_shutdown_function(function () { exit(2); }); } } else { // Web page foreach ($test->results() as $result) { if ($result['status']) { echo '<code style="color: darkgreen;">PASS</code>: ', $result['text'], "<br>\n"; } else { echo '<code style="color: red;">FAIL</code>: ', $result['text'], " - ", $result["source"], "<br>\n"; $err = true; } } if ($err) { echo "<p>One or more tests failed. Last error:<br>\n"; echo $f3->get("ERROR.text"), " at " . $f3->get("ERROR.trace.0.file"), ":", $f3->get("ERROR.trace.0.line"), "</p>\n"; if ($f3->get("DEBUG") >= 3) { foreach ($f3->get("ERROR.trace") as $line) { echo "<b>", $line["file"], "</b><br>"; echo $line["line"], ": ", $line["class"], $line["type"], $line["function"], "(", implode(", ", $line["args"]), ")<br>"; } } register_shutdown_function(function () { exit(2); }); } } }
function run() { $test = new \Test(); /** @var \Base $f3 */ $f3 = \Base::instance(); $news = new NewsModel(); $news->load(); $dummy = array('title' => 'copy test', 'text' => 'Lorem ipsum dolor sit amet.', 'author' => 1, 'tags' => array(3)); $f3->set('record1', $dummy); $news->copyto('record2'); $test->expect($f3->exists('record2'), 'copyto: raw record copied to hive'); $news->reset(); $news->copyfrom('record1'); $test->expect($news->title = 'copy test' && ($news->text = 'Lorem ipsum dolor sit amet.'), 'copyfrom: hydrate from hive key'); $test->expect($news->author instanceof AuthorModel && !$news->author->dry() && $news->tags instanceof \DB\CortexCollection, 'copyfrom: relations hydrated successful'); $test->expect($news->get('author', true) == 1, 'get raw data from relational field'); $news->reset(); $news->copyfrom('record2', 'title;author'); $test->expect($news->title = 'Responsive Images' && $news->get('author', true) == 2 && $news->text == NULL, 'copyfrom: limit fields with split-able string'); $news->reset(); $news->copyfrom('record2', array('title')); $test->expect($news->title = 'Responsive Images' && $news->text == NULL, 'copyfrom: limit fields by array'); $news->reset(); $news->copyfrom($dummy, function ($fields) { return array_intersect_key($fields, array_flip(array('title'))); }); $test->expect($news->title = 'copy test', 'copyfrom: copy from array instead of hive key'); $test->expect($news->title = 'copy test' && $news->text == NULL, 'copyfrom: limit fields by callback function'); $all = $news->find(); $allTitle = $all->getAll('title'); $test->expect(count($allTitle) == 3 && $allTitle[0] == 'Responsive Images' && $allTitle[1] == 'CSS3 Showcase' && $allTitle[2] == 'Touchable Interfaces', 'collection getAll returns all values of selected field'); $newsByID = $all->getBy('_id'); $test->expect(array_keys($newsByID) == array(1, 2, 3), 'collection getBy sorts by given field'); $newsByAuthorID = $all->getBy('author', true); $test->expect(array_keys($newsByAuthorID) == array(2, 1) && count($newsByAuthorID[2]) == 2 && count($newsByAuthorID[1]) == 1, 'collection getBy nested sort by author'); $allTitle = array(); foreach ($all as $record) { $allTitle[] = $record->title; } $test->expect(count($allTitle) == 3 && $allTitle[0] == 'Responsive Images' && $allTitle[1] == 'CSS3 Showcase' && $allTitle[2] == 'Touchable Interfaces', 'collection is traversable'); /////////////////////////////////// return $test->results(); }
$exampleId = $f3->content[0]["id"]; /****************CREATE ITEMS BLOCK********************/ /****************GET ITEMS BLOCK********************/ $f3->mock("GET @getAction(@id=-5)"); $test->expect($f3->content["success"] === false && $f3->content["error"] == "Organization has not been found", 'GET should return data only if customer exists / invalid request'); $f3->mock("GET @getAction(@id={$exampleId})"); $test->expect($f3->content["success"] === true && $daughtersCnt == count($f3->content["related_objects"]) - 1, 'GET should return data only if customer exists / valid request'); /****************GET ITEMS BLOCK********************/ /****************DELETE ITEMS BLOCK********************/ $f3->mock("DELETE @deleteAction"); $f3->mock("GET @getAction(@id={$exampleId})"); $test->expect($f3->content["success"] === false && $f3->content["error"] == "Organization has not been found", 'GET should return data only if customer exists / after delete'); /****************DELETE ITEMS BLOCK********************/ /****************OUTPUT BLOCK********************/ // Display the results; not MVC but let's keep it simple foreach ($test->results() as $result) { echo $result['text'] . '<br>'; if ($result['status']) { echo 'Pass'; } else { echo 'Fail (' . $result['source'] . ')'; } echo '<br>'; } /****************OUTPUT BLOCK********************/ function generateRandomString($length = 10) { $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $charactersLength = strlen($characters); $randomString = ''; for ($i = 0; $i < $length; $i++) {
function run($f3) { $test = new \Test(); $f3->config('tests/cfg/app.ini'); $this->routes = $f3->get('ROUTES'); //backup $this->aliases = $f3->get('ALIASES'); //backup $this->ml = new Multilang(); //plugin configuration $test->expect($this->ml->languages() == array('fr', 'it', 'de', 'en'), 'Languages definition'); $test->expect($this->ml->primary == 'fr' && $f3->get('FALLBACK') == 'fr-FR,fr', 'Primary language and FALLBACK correctly set'); //language detection $browser = 'da-DK,it-IT,en'; //simulate browser detection $f3->set('LANGUAGE', $browser); //reset browser language $this->simulate('/'); $test->expect($this->ml->auto && $this->ml->current == 'it' && $f3->get('LANGUAGE') == 'it-IT,it,fr-FR,fr', 'Browser detected language'); $this->simulate('/en/something', $this->ml); $test->expect(!$this->ml->auto && $this->ml->current == 'en' && $f3->get('LANGUAGE') == 'en,en-GB,fr-FR,fr', 'URL detected language'); $f3->set('LANGUAGE', $browser); //reset browser language $this->simulate('/da/noget'); $test->expect($this->ml->current == 'it' && $f3->get('LANGUAGE') == 'it-IT,it,fr-FR,fr', 'Unknown language (fallback on browser detection)'); //routes rewriting $routes =& $f3->ref('ROUTES'); $aliases =& $f3->ref('ALIASES'); $this->simulate('/de/etwas'); //german URL $test->expect(array_keys($routes) == array('/de', '/de/legal', '/de/faq', '/de/blog/@slug', '/resize/@format/@file', '/de/foo/@bar/*', '/') && array_values($aliases) == array('/de/legal', '/de/faq', '/de/blog/@slug', '/resize/@format/@file', '/de/foo/@bar/*'), 'ROUTES and ALIASES rewritten (auto)'); $this->simulate('/fr/quelque-chose'); //french URL $test->expect(array_keys($routes) == array('/fr', '/fr/mentions-legales', '/fr/foire-aux-questions', '/resize/@format/@file', '/fr/foo/@bar/*', '/') && array_values($aliases) == array('/fr/mentions-legales', '/fr/foire-aux-questions', '/resize/@format/@file', '/fr/foo/@bar/*'), 'ROUTES and ALIASES rewritten (custom)'); $test->expect(!array_key_exists('/fr/blog/@slug', $routes) && !array_key_exists('/blog/@slug', $routes) && !in_array('/fr/blog/@slug', $aliases) && !in_array('/blog/@slug', $aliases), 'Exclusions (excluded language)'); $this->simulate('/en/something'); //english URL $test->expect(array_key_exists('/en/blog/@slug', $routes) && !array_key_exists('/blog/@slug', $routes) && in_array('/en/blog/@slug', $aliases) && !in_array('/blog/@slug', $aliases), 'Exclusions (allowed language)'); //Global routes $test->expect(!array_key_exists('/fr/resize/@format/@file', $routes) && array_key_exists('/resize/@format/@file', $routes) && !in_array('/fr/resize/@format/@file', $aliases) && in_array('/resize/@format/@file', $aliases), 'Global route not rewritten (alias)'); $f3->set('MULTILANG.global', array('resize', '/blog')); $this->simulate('/en/something'); //english URL $test->expect(!array_key_exists('/en/blog/@slug', $routes) && array_key_exists('/blog/@slug', $routes) && !in_array('/en/blog/@slug', $aliases) && in_array('/blog/@slug', $aliases), 'Global route not rewritten (prefix)'); $f3->set('LANGUAGE', $browser); //reset browser language $this->simulate('/blog/foo'); $test->expect($this->ml->current == 'it', 'Language auto-detected on a global route'); $f3->set('MULTILANG.global', 'resize'); //routes testing $test->expect($this->mock('/fr/foire-aux-questions') == 'Bonjour faq' && $this->mock('/it/legal') == 'Ciao da Italia legal', 'Rewritten routes executed'); $f3->set('LANGUAGE', $browser); //reset browser language $test->expect($this->mock('/resize/120x80/foo.gif') == 'Ciao da Italia foo.gif', 'Global route executed'); $f3->set('ONREROUTE', function ($url, $permanent = FALSE) use($f3) { $f3->mock('GET ' . $url); }); $test->expect($this->mock('/') == 'Ciao da Italia home', 'Default root handler (browser detection)'); $f3->set('MULTILANG.root', function ($f3) { echo Multilang::instance()->current . ' detected'; }); $test->expect($this->mock('/') == 'it detected', 'Custom root handler'); $f3->clear('MULTILANG.root'); //alias function $this->simulate('/de/zehr-gut'); //german URL $test->expect($this->ml->alias('blogEntry', 'slug=hallo-welt') == '/de/blog/hallo-welt', 'Alias function (current language)'); $test->expect($this->ml->alias('blogEntry', 'slug=hello-world', 'en') == '/en/blog/hello-world', 'Alias function (target language)'); $test->expect($this->ml->alias('blogEntry', 'slug=bonjour', 'fr') === FALSE, 'Alias function (excluded route)'); $test->expect($this->ml->alias('resize', 'format=big,file=foo.gif') === '/resize/big/foo.gif' && $this->ml->alias('resize', 'format=big,file=foo.gif', 'it') === '/resize/big/foo.gif', 'Alias function (global route)'); //migration mode $f3->set('LANGUAGE', $browser); //reset browser language $f3->set('MULTILANG.migrate', TRUE); $f3->set('ONREROUTE', function ($url, $permanent) use($f3) { echo "rerouted to {$url}"; }); $test->expect($this->mock('/faq') == 'rerouted to /fr/foire-aux-questions' && $this->mock('/foo/bar/path/to/log') == 'rerouted to /fr/foo/bar/path/to/log', 'Migration mode: old URIs redirected to primary URIs'); $test->expect($this->mock('/') == 'rerouted to /it', 'Migration mode: root not redirected to primary URI (see MULTILANG.root)'); $f3->set('MULTILANG.migrate', FALSE); //rerouting $this->simulate('/de/zehr-gut'); //german URL $f3->set('ONREROUTE', function ($url, $permanent) use($f3) { $f3->set('rerouted', $url); }); $f3->clear('rerouted'); $this->ml->reroute('@blogEntry(slug=hallo-welt)'); $test->expect($f3->get('rerouted') == '/de/blog/hallo-welt', 'Reroute to a named rewritten route'); $f3->clear('rerouted'); $this->ml->reroute('@resize(format=big,file=foo.gif)'); $test->expect($f3->get('rerouted') == '/resize/big/foo.gif', 'Reroute to a named global route'); $ok = TRUE; $reroutes = array(NULL => $f3->REALM, '/' => '/de', '/blog/hallo-welt' => '/de/blog/hallo-welt', '/de/blog/hallo-welt' => '/de/blog/hallo-welt'); foreach ($reroutes as $url => $expected) { $f3->clear('rerouted'); $this->ml->reroute($url); $ok = $ok && $f3->get('rerouted') == $expected; } $test->expect($ok, 'Reroute to any unnamed route (auto prefix)'); //helper functions $test->expect($this->ml->isGlobal('resize') && !$this->ml->isGlobal('blogEntry'), 'Check if a route is global'); $test->expect($this->ml->isLocalized('blogEntry') && !$this->ml->isLocalized('blogEntry', 'fr'), 'Check if a route is localized'); $intl = class_exists('Locale'); $test->expect($this->ml->displayLanguage('fr') == ($intl ? 'Französisch' : 'French'), sprintf('Display a language name (%s intl extension)', $intl ? 'with' : 'without')); $test->expect($this->ml->displayCountry('fr') == ($intl ? 'Frankreich' : 'France'), sprintf('Display a country name (%s intl extension)', $intl ? 'with' : 'without')); $f3->set('results', $test->results()); }
function run() { $test = new \Test(); /** @var \Base $f3 */ $f3 = \Base::instance(); $news = new NewsModel(); $news->load(); $dummy = array('title' => 'copy test', 'text' => 'Lorem ipsum dolor sit amet.', 'author' => 1, 'tags' => array(3)); $f3->set('record1', $dummy); $news->copyto('record2'); $test->expect($f3->exists('record2'), 'copyto: raw record copied to hive'); $news->reset(); $news->copyfrom('record1'); $test->expect($news->title = 'copy test' && ($news->text = 'Lorem ipsum dolor sit amet.'), 'copyfrom: hydrate from hive key'); $test->expect($news->author instanceof AuthorModel && !$news->author->dry() && $news->tags instanceof \DB\CortexCollection, 'copyfrom: relations hydrated successful'); $test->expect($news->get('author', true) == 1, 'get raw data from relational field'); $news->reset(); $news->copyfrom('record2', 'title;author'); $test->expect($news->title = 'Responsive Images' && $news->get('author', true) == 2 && $news->text == NULL, 'copyfrom: limit fields with split-able string'); $news->reset(); $news->copyfrom('record2', array('title')); $test->expect($news->title = 'Responsive Images' && $news->text == NULL, 'copyfrom: limit fields by array'); $news->reset(); $news->copyfrom($dummy, function ($fields) { return array_intersect_key($fields, array_flip(array('title'))); }); $test->expect($news->title = 'copy test', 'copyfrom: copy from array instead of hive key'); $test->expect($news->title = 'copy test' && $news->text == NULL, 'copyfrom: limit fields by callback function'); $all = $news->find(); $allTitle = $all->getAll('title'); $test->expect(count($allTitle) == 3 && $allTitle[0] == 'Responsive Images' && $allTitle[1] == 'CSS3 Showcase' && $allTitle[2] == 'Touchable Interfaces', 'collection getAll returns all values of selected field'); $newsByID = $all->getBy('_id'); $test->expect(array_keys($newsByID) == array(1, 2, 3), 'collection getBy sorts by given field'); $newsByAuthorID = $all->getBy('author', true); $test->expect(array_keys($newsByAuthorID) == array(2, 1) && count($newsByAuthorID[2]) == 2 && count($newsByAuthorID[1]) == 1, 'collection getBy nested sort by author'); $allTitle = array(); foreach ($all as $record) { $allTitle[] = $record->title; } $test->expect(count($allTitle) == 3 && $allTitle[0] == 'Responsive Images' && $allTitle[1] == 'CSS3 Showcase' && $allTitle[2] == 'Touchable Interfaces', 'collection is traversable'); $news->reset(); $news->load(); $r = $news->cast(null, 0); $test->expect($r['tags2'] == null && is_int($r['author']), 'simple cast without relations'); $r = $news->cast(null, 1); $test->expect(is_array($r['tags2']) && is_array($r['author']), '1-level nested cast'); $r = $news->cast(null, 2); $test->expect(is_array($r['author']['profile']), '2-level nested cast'); $r = $news->cast(null, array('*' => 2)); $test->expect(is_array($r['author']['profile']), '2-level nested cast, alternative'); $r = $news->cast(null, array('*' => 0, 'author' => 0)); $test->expect(is_array($r['author']) && $r['tags2'] == null && $r['author']['news'] == null && $r['author']['profile'] == null, 'custom cast'); $r = $news->cast(null, array('*' => 0, 'author' => array('*' => 1))); $test->expect(is_array($r['author']) && $r['tags2'] == null && is_array($r['author']['news']) && is_array($r['author']['profile']), 'custom nested cast'); $r = $news->cast(null, array('*' => 0, 'author' => array('*' => 0, 'profile' => 0))); $test->expect(is_array($r['author']) && $r['tags2'] == null && $r['author']['news'] == null && is_array($r['author']['profile']) && is_int($r['author']['profile']['author']), 'custom nested cast with exclusions'); $r = $news->cast(null, array('*' => 0, 'author' => array('*' => 0, 'profile' => 1))); $test->expect(is_array($r['author']) && $r['tags2'] == null && $r['author']['news'] == null && is_array($r['author']['profile']) && is_array($r['author']['profile']['author']), 'custom multi-level nested cast'); $filterA = array('foo1 = ? and bar1 = ?', 10, 20); $filterB = array('foo2 = ? and bar2 = ?', 30, 40); $filterC = array('foo3 = ? and bar3 = ?', 50, 60); $filter = $news->mergeFilter(array($filterA, $filterB, $filterC), 'or'); $test->expect($filter == array('( foo1 = ? and bar1 = ? ) or ( foo2 = ? and bar2 = ? ) or ( foo3 = ? and bar3 = ? )', 10, 20, 30, 40, 50, 60), 'merge multiple filters'); /////////////////////////////////// return $test->results(); }
function run($db, $type) { $test = new \Test(); // clear existing data \AuthorModel::setdown(); \TagModel::setdown(); \NewsModel::setdown(); \ProfileModel::setdown(); // setup models \AuthorModel::setup(); \TagModel::setup(); \NewsModel::setup(); \ProfileModel::setup(); // setup Author /////////////////////////////////// $author_id = array(); $author = new \AuthorModel(); $ac = $author::resolveConfiguration(); $author_pk = is_int(strpos($type, 'sql')) ? $ac['primary'] : '_id'; $author->name = 'Johnny English'; $author->save(); $author_id[] = $author->_id; $author->reset(); $author->name = 'Ridley Scott'; $author->save(); $author_id[] = $author->_id; $author->reset(); $author->name = 'James T. Kirk'; $author->save(); $author_id[] = $author->_id; $author->reset(); $allauthors = $author->find()->castAll(); $allauthors = $this->getResult($allauthors); $test->expect(json_encode($allauthors) == '[{"name":"Johnny English"},{"name":"Ridley Scott"},{"name":"James T. Kirk"}]', $type . ': all AuthorModel items created'); // setup Tags /////////////////////////////////// $tag_id = array(); $tag = new \TagModel(); $tc = $tag::resolveConfiguration(); $tag_pk = is_int(strpos($type, 'sql')) ? $tc['primary'] : '_id'; $tag->title = 'Web Design'; $tag->save(); $tag_id[] = $tag->_id; $tag->reset(); $tag->title = 'Responsive'; $tag->save(); $tag_id[] = $tag->_id; $tag->reset(); $tag->title = 'Usability'; $tag->save(); $tag_id[] = $tag->_id; $tag->reset(); $allTags = $this->getResult($tag->find()); $test->expect(json_encode($allTags) == '[{"title":"Web Design"},{"title":"Responsive"},{"title":"Usability"}]', $type . ': all TagModel items created'); // setup News /////////////////////////////////// $news_id = array(); $news = new \NewsModel(); $nc = $news::resolveConfiguration(); $news_pk = is_int(strpos($type, 'sql')) ? $nc['primary'] : '_id'; $news->title = 'Responsive Images'; $news->text = 'Lorem Ipsun'; $news->save(); $news_id[] = $news->_id; $news->reset(); $news->title = 'CSS3 Showcase'; $news->text = 'News Text 2'; $news->save(); $news_id[] = $news->_id; $news->reset(); $news->title = 'Touchable Interfaces'; $news->text = 'Lorem Foo'; $news->save(); $news_id[] = $news->_id; $news->reset(); $allnews = $this->getResult($news->find(null, array('order' => 'title'))); $test->expect(count($allnews) == 3 && $allnews[0]['title'] == 'CSS3 Showcase' && $allnews[1]['title'] == 'Responsive Images' && $allnews[2]['title'] == 'Touchable Interfaces', $type . ': all NewsModel items created'); // belongs-to author relation /////////////////////////////////// $author->load(); $news->load(array($news_pk . ' = ?', $news_id[0])); $news->author = $author; $news->save(); $news->reset(); $news->load(array($news_pk . ' = ?', $news_id[0])); $test->expect($news->author->name == 'Johnny English', $type . ': belongs-to-one: author relation created'); $news->author = NULL; $news->save(); $news->reset(); $news->load(array($news_pk . ' = ?', $news_id[0])); $test->expect(empty($news->author), $type . ': belongs-to-one: author relation released'); $news->author = $author->_id; $news->save(); $news->reset(); $news->load(array($news_pk . ' = ?', $news_id[0])); $test->expect($news->author->name == 'Johnny English', $type . ': belongs-to-one: relation created by raw id'); // belongs-to-many tag relation /////////////////////////////////// $tag1 = new \TagModel(); $tag1->load(array($tag_pk . ' = ?', $tag_id[0])); $tag2 = new \TagModel(); $tag2->load(array($tag_pk . ' = ?', $tag_id[1])); $news->tags = array($tag1, $tag2); $news->save(); $news->reset(); $news->load(array($news_pk . ' = ?', $news_id[0])); $test->expect($news->tags[0]->title == 'Web Design' && $news->tags[1]->title == 'Responsive', $type . ': belongs-to-many: relations created with array of mapper objects'); $news->reset(); $news->load(array($news_pk . ' = ?', $news_id[1])); $news->tags = array($tag_id[1], $tag_id[2]); $news->save(); $news->reset(); $news->load(array($news_pk . ' = ?', $news_id[1])); $test->expect(count($news->tags) == 2 && $news->tags[0]->title == 'Responsive' && $news->tags[1]->title == 'Usability', $type . ': belongs-to-many: relations created with array of IDs'); $news->tags = null; $news->save(); $news->reset(); $news->load(array($news_pk . ' = ?', $news_id[1])); $test->expect(empty($news->tags), $type . ': belongs-to-many: relations released'); $tag->reset(); $news->load(array($news_pk . ' = ?', $news_id[1])); $news->tags = $tag->load(array($tag_pk . ' != ?', $tag_id[0])); $news->save(); $news->reset(); $news->load(array($news_pk . ' = ?', $news_id[1])); $test->expect($news->tags[0]->title == 'Responsive' && $news->tags[1]->title == 'Usability', $type . ': belongs-to-many: relations created with hydrated mapper'); $news->reset(); $tag->reset(); $news->load(array($news_pk . ' = ?', $news_id[2])); $news->tags = $tag_id[0] . ';' . $tag_id[2]; $news->save(); $news->reset(); $news->load(array($news_pk . ' = ?', $news_id[2])); $test->expect($news->tags[0]->title == 'Web Design' && $news->tags[1]->title == 'Usability', $type . ': belongs-to-many: relations created with split-able string'); $test->expect(is_object($news->tags) && $news->tags instanceof \DB\CortexCollection, $type . ': belongs-to-many: result is collection'); // has-one relation /////////////////////////////////// $profile = new ProfileModel(); $pc = $profile::resolveConfiguration(); $profile_pk = is_int(strpos($type, 'sql')) ? $pc['primary'] : '_id'; $profile->message = 'Hello World'; $profile->author = $author->load(array($author_pk . ' = ?', $author_id[0])); $profile->save(); $profile_id = $profile->_id; $profile->reset(); $author->reset(); $author->load(array($author_pk . ' = ?', $author_id[0])); $profile->load(array($profile_pk . ' = ?', $profile_id)); $test->expect($author->profile->message == 'Hello World' && $profile->author->name == "Johnny English", $type . ': has-one: relation assigned'); $profile->reset(); $profile->message = 'I\'m feeling lucky'; $profile->image = 'lolcat.jpg'; $author->reset(); $author->load(array($author_pk . ' = ?', $author_id[1])); $author->profile = $profile; $author->save(); $profile->reset(); $author->reset(); $author->load(array($author_pk . ' = ?', $author_id[1])); $test->expect($author->profile->message == 'I\'m feeling lucky', $type . ': has-one: inverse relation'); // has-many relation /////////////////////////////////// $author->load(array($author_pk . ' = ?', $author_id[0])); $result = $this->getResult($author->news); $test->expect($result[0]['title'] == "Responsive Images" && $result[0]['tags'][0]['title'] == 'Web Design' && $result[0]['tags'][1]['title'] == 'Responsive', $type . ': has-many inverse relation'); // many to many relation /////////////////////////////////// $news->load(array($news_pk . ' = ?', $news_id[0])); $news->tags2 = array($tag_id[0], $tag_id[1]); $news->save(); $news->reset(); $news->load(array($news_pk . ' = ?', $news_id[0])); $test->expect($news->tags2[0]['title'] == 'Web Design' && $news->tags2[1]['title'] == 'Responsive', $type . ': many-to-many relation created'); $test->expect(is_object($news->tags2) && $news->tags2 instanceof \DB\CortexCollection, $type . ': many-to-many: result is collection'); $tag3 = $tag->load(array($tag_pk . ' = ?', $tag_id[2])); $news->tags2[] = $tag3; $news->save(); $a = count($news->tags2); $news->reset(); $news->load(array($news_pk . ' = ?', $news_id[0])); $test->expect(count($news->tags2) == 3 && $a == 3, $type . ': many-to-many relation added implicitly'); $news->load(array($news_pk . ' = ?', $news_id[0])); $news->tags2 = NULL; $news->save(); $news->reset(); $news->load(array($news_pk . ' = ?', $news_id[0])); $test->expect(is_null($news->tags2), $type . ': many-to-many relation released'); $news->reset(); $news->title = 'Can it run Crysis?'; $news->text = 'XOXO'; $news->tags2 = array($tag_id[0]); $news->save(); $news_id[] = $news->_id; $news->reset(); $news->load(array($news_pk . ' = ?', $news_id[3])); $a = count($news->tags2); $tag1 = $tag->find(array($tag_pk . ' = ?', $tag_id[0])); $b = count($tag1[0]->news); $c = $tag1[0]->news[0]->title == 'Can it run Crysis?'; $news->erase(); $tag1 = $tag->find(array($tag_pk . ' = ?', $tag_id[0])); $d = count($tag1[0]->news); $test->expect($a == 1 && $b == 1 && $c && $d == 0, $type . ': many-to-many relation cleaned by erase cascade'); $news->load(array($news_pk . ' = ?', $news_id[0])); $all = $news->find(); $test->expect($all[1]->tags2 === NULL && $all[2]->author === NULL, $type . ': empty relations are NULL'); $arr = $news->cast(); $test->expect(is_array($arr['tags']), $type . ': collection becomes array in casted model'); if ($type == 'mongo') { $test->expect(is_string($arr['_id']), $type . ': id becomes string in casted model'); } /////////////////////////////////// return $test->results(); }
function run($f3) { $test = new \Test(); Registry::clear('Cron'); //plugin config $f3->config('tests/jobs.ini'); $f3->mset(array('log' => FALSE, 'cli' => TRUE, 'web' => FALSE, 'clipath' => 'index.php'), 'CRON.'); $cron = Cron::instance(); $test->expect(!$cron->log && $cron->cli && !$cron->web && count($cron->jobs) == 3 && $cron->clipath == 'index.php', 'Initial config'); $test->expect($cron->jobs['JobC'][1] === '*/2 0-23 1-3,4-5 4,5,6 *', 'Expression containing commas correctly restored'); $test->expect(isset($f3->ROUTES['/cron']), 'Route automatically defined'); //async auto-detection $async = function_exists('exec') && exec('php -r "echo 1+3;"') == '4'; $test->expect($cron->async === $async, 'Async auto-detection: ' . ($async ? 'ON' : 'OFF')); //expression parsing $test->expect($cron->parseExpr('1 2 3,9 4 5') === array(array(1), array(2), array(3, 9), array(4), array(5)), 'parseExpr(): single values'); $test->expect($cron->parseExpr('1-3 2-5 3-2,1 4 *') === array(array(1, 2, 3), array(2, 3, 4, 5), array(3, 2, 1), array(4), range(0, 6)), 'parseExpr(): ranges and wildcards'); $test->expect($cron->parseExpr('0,1-6/2,10 2 0-10/3 4 */3') === array(array(0, 1, 3, 5, 10), array(2), array(0, 3, 6, 9), array(4), array(0, 3, 6)), 'parseExpr(): step values on ranges and wildcards'); $test->expect($cron->parseExpr('@yearly') === $cron->parseExpr('0 0 1 1 *') && $cron->parseExpr('@annually') === $cron->parseExpr('0 0 1 1 *') && $cron->parseExpr('@monthly') === $cron->parseExpr('0 0 1 * *') && $cron->parseExpr('@weekly') === $cron->parseExpr('0 0 * * 0') && $cron->parseExpr('@daily') === $cron->parseExpr('0 0 * * *') && $cron->parseExpr('@hourly') === $cron->parseExpr('0 * * * *') && $cron->parseExpr('@weekend') === $cron->parseExpr('0 8 * * 6') && $cron->parseExpr('@lunch') === $cron->parseExpr('0 12 * * *'), 'parseExpr(): schedule presets'); $test->expect($cron->parseExpr('1 2 3 4') === FALSE && $cron->parseExpr('*-2 2 3 4 5') === FALSE && $cron->parseExpr('1 2 3-5/4 4 5') === FALSE && $cron->parseExpr('1 2 3 4 sun') === FALSE && $cron->parseExpr('@dinner') === FALSE, 'parseExpr(): invalid expressions or presets return FALSE'); //timestamp parsing $time = mktime(1, 2, 3, 4, 5, 2015); // 2015-04-05 01:02:03 (Saturday) $test->expect($cron->parseTimestamp($time) == array(2, 1, 5, 4, 0), 'parseTimestamp()'); //due check $test->expect($cron->isDue('JobA', $time) && $cron->isDue('JobB', $time) && $cron->isDue('JobC', $time), 'isDue() returns TRUE if the requested job is due at the given time'); $test->expect($cron->isDue('Foo', $time) === FALSE || $cron->isDue('joba', $time) === FALSE, 'isDue() returns FALSE if the requested job doesn\'t exist or if the case doesn\'t match'); $cron->set('JobD', function () use($f3) { $f3->job .= 'D'; }, '* * 4 * *'); $cron->set('JobE', 'Jobs->jobE', '2 1 5 4 1'); $cron->set('JobF', 'Jobs->jobF', '*/3 * * * *'); $test->expect(!$cron->isDue('JobD', $time) && !$cron->isDue('JobE', $time) && !$cron->isDue('JobF', $time), 'isDue() returns FALSE if the requested job is not due at the given time'); //job execution $f3->job = ''; $cron->execute('JobA', FALSE); $cron->execute('JobD', FALSE); $test->expect($f3->job === 'AD', 'Serial job execution'); $cron->execute('JobC', FALSE); $test->expect(TRUE, 'Silently fail on a non-existing job handler'); //logging @unlink($logfile = $f3->LOGS . 'cron.log'); $cron->log = TRUE; $cron->execute('JobA', FALSE); $cron->log = FALSE; $test->expect(file_exists($logfile) && count(file($logfile)) == 1, 'Logging to file'); //schedule running $f3->job = ''; $cron->run($time, FALSE); $test->expect($f3->job = 'AB', 'Run scheduler, i.e executes all due jobs'); //async job execution if ($async) { $cron->set('test1', 'Jobs->test1', '* * * * *'); $cron->set('test2', 'Jobs->test2', '* * * * *'); @unlink($testfile = $f3->TEMP . 'cron-test.txt'); $cron->execute('test1', TRUE); $cron->execute('test2', TRUE); $async_ok = FALSE; //wait for async processes to complete $start = microtime(TRUE); $loop = array(0.1, 4); //loop (step=0.1s / max=4s) while (microtime(TRUE) - $start < $loop[1]) { usleep($loop[0] * 1000000); if (file_exists($testfile) && preg_match('/([ABCD]){4}/', file_get_contents($testfile), $m) && array_unique($m) === $m) { $async_ok = TRUE; break; } } $test->expect($async_ok, 'Parallel job execution'); } //web access forbidden by default $f3->HALT = FALSE; $f3->clear('ERROR'); $f3->ONERROR = function () { }; $f3->mock('GET /cron'); $test->expect(isset($f3->ERROR['code']) && $f3->ERROR['code'] === 404, 'Web access forbidden by default'); $f3->set('results', $test->results()); }
} $testMin = 1; $testMax = 100000; $tests = 50; $print = true; $verbose = false; $filename = __DIR__ . '/data/minhash/results.' . date('Y-m-d_h-i-s') . '.csv'; file_put_contents($filename, ''); for ($i = $testMin; $i <= $testMax; $i += $block) { $block = pow(10, max(0, floor(log10($i)))); $test = new Test($i); $test->test($tests); if ($print) { if ($verbose) { testHeader('Tested: ' . $i); printResults($test->results()); } testHeader('Average: ' . $i); printResults(array($test->averages())); } fileResults($filename, $test->results()); echo "Tested {$i}\r"; } echo PHP_EOL; class Test { private $i; private $average = array(0, 0, 0); private $results = array(); public function __construct($i) {
function run($db, $type) { $test = new \Test(); // setup /////////////////////////////////// $author = new \AuthorModel(); $news = new \NewsModel(); $profile = new \ProfileModel(); $tag = new \TagModel(); $ac = $author::resolveConfiguration(); $author_pk = is_int(strpos($type, 'sql')) ? $ac['primary'] : '_id'; $nc = $news::resolveConfiguration(); $news_pk = is_int(strpos($type, 'sql')) ? $nc['primary'] : '_id'; $tc = $tag::resolveConfiguration(); $tag_pk = is_int(strpos($type, 'sql')) ? $tc['primary'] : '_id'; $authorIDs = $author->find()->getAll('_id'); $all = $news->find(); $newsIDs = $all->getAll('_id'); $profileIDs = $profile->find()->getAll('_id'); $tagIDs = $tag->find()->getAll('_id'); // add another relation $news->load(array('title = ?', 'CSS3 Showcase')); $news->author = $author->load(array($author_pk . ' = ?', $authorIDs[0])); $news->save(); $news->reset(); $author->reset(); // has-filter on belongs-to relation /////////////////////////////////// $result = $author->has('news', array('title like ?', '%Image%'))->afind(); $test->expect(count($result) == 1 && $result[0]['name'] == 'Johnny English', $type . ': has filter on many-to-one field'); $test->expect(count($result[0]['news']) == 2 && $result[0]['news'][0]['title'] == 'Responsive Images' && $result[0]['news'][1]['title'] == 'CSS3 Showcase', $type . ': has filter does not prune relation set'); $result = $news->has('author', array('name = ?', 'Johnny English'))->afind(); $test->expect(count($result) == 2 && $result[0]['title'] == 'Responsive Images' && $result[1]['title'] == 'CSS3 Showcase', $type . ': has filter on one-to-many field'); // add another profile $profile->message = 'Beam me up, Scotty!'; $profile->author = $authorIDs[2]; $profile->save(); $profile->reset(); $result = $author->has('profile', array('message LIKE ?', '%Scotty%'))->afind(); $test->expect(count($result) == 1 && $result[0]['name'] == 'James T. Kirk' && $result[0]['profile']['message'] == 'Beam me up, Scotty!', $type . ': has filter on one-to-one field'); $result = $profile->has('author', array('name LIKE ?', '%Kirk%'))->afind(); $test->expect(count($result) == 1 && $result[0]['message'] == 'Beam me up, Scotty!' && $result[0]['author']['name'] == 'James T. Kirk', $type . ': has filter on one-to-one field, inverse'); // add mm tags $news->load(array('title = ?', 'Responsive Images')); $news->tags2 = array($tagIDs[0], $tagIDs[1]); $news->save(); $news->load(array('title = ?', 'CSS3 Showcase')); $news->tags2 = array($tagIDs[1], $tagIDs[2]); $news->save(); $news->reset(); $result = $news->has('tags2', array('title like ?', '%Design%'))->find(); $test->expect(count($result) == 1 && $result[0]['title'] == 'Responsive Images', $type . ': has filter on many-to-many field'); $result = $news->has('tags2', array('title = ?', 'Responsive'))->find(); $test->expect(count($result) == 2 && $result[0]['title'] == 'Responsive Images' && $result[1]['title'] == 'CSS3 Showcase', $type . ': has filter on many-to-many field, additional test'); $result = $tag->has('news', array('title = ?', 'Responsive Images'))->find(); $test->expect(count($result) == 2 && $result[0]['title'] == 'Web Design' && $result[1]['title'] == 'Responsive', $type . ': has filter on many-to-many field, inverse'); // add another tag $news->load(array('title = ?', 'Touchable Interfaces')); $news->tags2 = array($tagIDs[1]); $news->save(); $news->reset(); $tag->has('news', array('text LIKE ? and title LIKE ?', '%Lorem%', '%Interface%')); $result = $tag->find(); $test->expect(count($result) == 1 && $result[0]['title'] == 'Responsive', $type . ': has filter with multiple conditions'); $news->has('tags2', array('title = ? OR title = ?', 'Usability', 'Web Design')); $result = $news->afind(array('text = ?', 'Lorem Ipsun')); $test->expect(count($result) == 1 && $result[0]['title'] == 'Responsive Images', $type . ': find with condition and has filter'); $news->load(array('title = ?', 'Responsive Images')); $news->author = $authorIDs[1]; $news->save(); $news->reset(); $news->has('tags2', array('title = ? OR title = ?', 'Usability', 'Web Design')); $news->has('author', array('name = ?', 'Ridley Scott')); $result = $news->afind(); $test->expect(count($result) == 1 && $result[0]['title'] == 'Responsive Images', $type . ': find with multiple has filters on different relations'); // add another news to author 2 $news->load(array($news_pk . ' = ?', $newsIDs[2])); $news->author = $authorIDs[1]; $news->save(); $news->reset(); $news->has('author', array('name = ?', 'Ridley Scott')); $news->load(); $res = array(); while (!$news->dry()) { $res[] = $news->title; $news->next(); } $test->expect(count($res) == 2 && $res[0] == 'Responsive Images' && $res[1] == 'Touchable Interfaces', $type . ': has filter in load context'); $news->reset(); $news->fields(array('title')); $news->load(); $test->expect(!empty($news->title) && empty($news->author) && empty($news->text) && empty($news->tags) && empty($news->tags2), $type . ': use a whitelist to restrict fields'); unset($news); $news = new \NewsModel(); $news->fields(array('title', 'tags', 'tags2', 'author'), true); $news->load(); $test->expect(empty($news->title) && empty($news->author) && !empty($news->text) && empty($news->tags) && empty($news->tags2), $type . ': use a blacklist to restrict fields'); unset($news); $news = new \NewsModel(); $news->fields(array('tags.title')); $news->load(); $test->expect(!empty($news->tags[0]->title) && empty($news->tags[0]->news), $type . ': set restricted fields to related mappers'); $news->filter('tags2', null, array('order' => 'title ASC')); $news->load(array('title = ?', 'Responsive Images')); $test->expect($news->tags2[0]->title == 'Responsive' && $news->tags2[1]->title == 'Web Design', $type . ': filter with sorting of related records'); // get all tags sorted by their usage in news articles $tag->reset(); $tag->countRel('news'); $result = $tag->find(null, array('order' => 'count_news DESC, title'))->castAll(0); $test->expect($result[0]['title'] == 'Responsive' && $result[0]['count_news'] == 3 && $result[1]['title'] == 'Usability' && $result[1]['count_news'] == 1 && $result[2]['title'] == 'Web Design' && $result[2]['count_news'] == 1, $type . ': count and sort on many-to-many relation'); // get all authors sorted by the amount of news they have written $author->reset(); $author->countRel('news'); $result = $author->find(null, array('order' => 'count_news DESC'))->castAll(0); $test->expect($result[0]['name'] == 'Ridley Scott' && $result[0]['count_news'] == 2 && $result[1]['name'] == 'Johnny English' && $result[1]['count_news'] == 1 && $result[2]['name'] == 'James T. Kirk' && $result[2]['count_news'] == null, $type . ': count and sort on one-to-many relation'); $tag->reset(); $tag->countRel('news'); $result = $tag->find(null, array('order' => 'count_news DESC, title DESC', 'limit' => 1, 'offset' => 1))->castAll(0); $test->expect($result[0]['title'] == 'Web Design' && $result[0]['count_news'] == 1, $type . ': apply limit and offset on aggregated collection'); $author->reset(); $author->countRel('news'); $author->has('news', array('text like ?', '%Lorem%')); $result = $author->find()->castAll(0); $test->expect(count($result) == 1 && $result[0]['name'] == 'Ridley Scott' && $result[0]['count_news'] == 2, $type . ': has-filter and 1:M relation counter'); $author->reset(); $id = $author->load()->next()->_id; $tag->reset(); $tag->countRel('news'); $tag->has('news', array('author = ?', $id)); $result = $tag->find(null, array('order' => 'count_news desc'))->castAll(0); $test->expect(count($result) == 2 && $result[0]['title'] == 'Responsive' && $result[0]['count_news'] == 3 && $result[1]['title'] == 'Web Design' && $result[1]['count_news'] == 1, $type . ': has-filter and M:M relation counter'); /////////////////////////////////// return $test->results(); }
function run($db, $type) { $test = new \Test(); $tname = 'test_cortex'; \DB\Cortex::setdown($db, $tname); $fields = array('title' => array('type' => \DB\SQL\Schema::DT_TEXT), 'num1' => array('type' => \DB\SQL\Schema::DT_INT4), 'num2' => array('type' => \DB\SQL\Schema::DT_INT4)); \DB\Cortex::setup($db, $tname, $fields); // adding some testing data $cx = new \DB\Cortex($db, $tname); $cx->title = 'bar1'; $cx->save(); $cx->reset(); $cx->title = 'baz2'; $cx->num1 = 1; $cx->save(); $cx->reset(); $cx->title = 'foo3'; $cx->num1 = 4; $cx->save(); $cx->reset(); $cx->title = 'foo4'; $cx->num1 = 3; $cx->save(); $cx->reset(); $cx->title = 'foo5'; $cx->num1 = 3; $cx->num2 = 5; $cx->save(); $cx->reset(); $cx->title = 'foo6'; $cx->num1 = 3; $cx->num2 = 1; $cx->save(); $cx->reset(); $cx->title = 'foo7'; $cx->num1 = 3; $cx->num2 = 10; $cx->save(); $cx->reset(); $cx->title = 'foo8'; $cx->num1 = 5; $cx->save(); $cx->reset(); $cx->title = 'foo9'; $cx->num1 = 8; $cx->save(); $cx->reset(); $result = $this->getResult($cx->find()); $expected = array(0 => array('title' => 'bar1'), 1 => array('num1' => 1, 'title' => 'baz2'), 2 => array('num1' => 4, 'title' => 'foo3'), 3 => array('num1' => 3, 'title' => 'foo4'), 4 => array('num1' => 3, 'num2' => 5, 'title' => 'foo5'), 5 => array('num1' => 3, 'num2' => 1, 'title' => 'foo6'), 6 => array('num1' => 3, 'num2' => 10, 'title' => 'foo7'), 7 => array('num1' => 5, 'title' => 'foo8'), 8 => array('num1' => 8, 'title' => 'foo9')); $test->expect(json_encode($result) == json_encode($expected), $type . ': init mapper, adding records'); // operator = $result = $this->getResult($cx->find(array('title = ?', 'foo7'))); $expected = array(0 => array('num1' => 3, 'num2' => 10, 'title' => 'foo7')); $test->expect(json_encode($result) == json_encode($expected), $type . ': operator check: ='); // operator > $result = $this->getResult($cx->find(array('num1 > ?', 4))); $expected = array(0 => array('num1' => 5, 'title' => 'foo8'), 1 => array('num1' => 8, 'title' => 'foo9')); $test->expect(json_encode($result) == json_encode($expected), $type . ': operator check: >'); // operator >= $result = $this->getResult($cx->find(array('num1 >= ?', 5))); $test->expect(json_encode($result) == json_encode($expected), $type . ': operator check: >='); // operator < $result = $this->getResult($cx->find(array('num2 < ?', 2))); $expected = array(0 => array('num1' => 3, 'num2' => 1, 'title' => 'foo6')); $test->expect(json_encode($result) == json_encode($expected), $type . ': operator check: <'); // operator <= $result = $this->getResult($cx->find(array('num2 <= ?', 1))); $test->expect(json_encode($result) == json_encode($expected), $type . ': operator check: <='); // operator without binding $result = $this->getResult($cx->find(array('num1 > 4'))); $expected = array(0 => array('num1' => 5, 'title' => 'foo8'), 1 => array('num1' => 8, 'title' => 'foo9')); $test->expect(json_encode($result) == json_encode($expected), $type . ': operator without binding'); // field comparision $result = $this->getResult($cx->find(array('num2 > num1', 1))); $expected = array(0 => array('num1' => 3, 'num2' => 5, 'title' => 'foo5'), 1 => array('num1' => 3, 'num2' => 10, 'title' => 'foo7')); $test->expect(json_encode($result) == json_encode($expected), $type . ': check field comparision'); // lookahead search $result = $this->getResult($cx->find(array('title like ?', '%o6'))); $expected = array(0 => array('num1' => 3, 'num2' => 1, 'title' => 'foo6')); $test->expect(json_encode($result) == json_encode($expected), $type . ': lookahead search'); // lookbehind search $result = $this->getResult($cx->find(array('title like ?', 'bar%'))); $expected = array(0 => array('title' => 'bar1')); $test->expect(json_encode($result) == json_encode($expected), $type . ': lookbehind search'); // full search $result = $this->getResult($cx->find(array('title like ?', '%a%'))); $expected = array(0 => array('title' => 'bar1'), 1 => array('num1' => 1, 'title' => 'baz2')); $test->expect(json_encode($result) == json_encode($expected), $type . ': full search'); // negated search $result = $this->getResult($cx->find(array('title not like ?', 'foo%'))); $test->expect(json_encode($result) == json_encode($expected), $type . ': negated search'); // AND / OR chaining $result = $this->getResult($cx->find(array('(num2 < ? AND num1 > ?) OR title like ?', 2, 1, '%o9'))); $expected = array(0 => array('num1' => 3, 'num2' => 1, 'title' => 'foo6'), 1 => array('num1' => 8, 'title' => 'foo9')); $test->expect(json_encode($result) == json_encode($expected), $type . ': check logical operator chaining'); // check limit $result = $this->getResult($cx->find(null, array('limit' => '2'))); $expected = array(0 => array('title' => 'bar1'), 1 => array('num1' => 1, 'title' => 'baz2')); $test->expect(json_encode($result) == json_encode($expected), $type . ': check limit'); // check order $result = $this->getResult($cx->find(array('num2 >= ?', 1), array('order' => 'num2 desc'))); $expected = array(0 => array('num1' => 3, 'num2' => 10, 'title' => 'foo7'), 1 => array('num1' => 3, 'num2' => 5, 'title' => 'foo5'), 2 => array('num1' => 3, 'num2' => 1, 'title' => 'foo6')); $test->expect(json_encode($result) == json_encode($expected), $type . ': check order'); // IN search $rc = $cx->find(array('num1 IN ?', array(4, 5, 8))); $result = $rc->getAll('title'); sort($result); $test->expect(json_encode($result) == json_encode(array('foo3', 'foo8', 'foo9')), $type . ': IN operator'); $rc = $cx->find(array('num1 IN ? && num2 > ? && num2 NOT IN ?', array(3, 4), 1, array(10))); $result = $rc->getAll('title'); $test->expect(json_encode($result) == json_encode(array('foo5')), $type . ': enhanced IN, NOT IN operator'); /////////////////////////////////// return $test->results(); }
function run($f3) { $test = new \Test(); //Default policy: allow $access = new \Access(); $access->policy('allow'); $access->deny('/back', '*'); $access->allow('/back', 'admin,prod'); $access->deny('/back/users', 'prod'); $test->expect($access::ALLOW == $access->policy(), 'Default policy: ' . $access::ALLOW); $test->expect($access->granted('GET /blog', 'client'), 'Access granted by default'); $test->expect(!$access->granted('GET /back', 'client'), 'Access to a specific path denied to all'); $test->expect($access->granted('GET /back', 'prod'), 'Access to a path granted to a specific subject'); $test->expect(!$access->granted('GET /back/users', 'prod'), 'Access to a subpath denied to a specific subject'); //Default policy: deny $access = new \Access(); $access->policy('deny'); $access->allow('/admin', 'admin,prod'); $access->deny('/admin/part2', '*'); $access->allow('/admin/part2', 'admin'); $test->expect($access::DENY == $access->policy(), 'Default policy: ' . $access::DENY); $test->expect(!$access->granted('GET /blog', 'client'), 'Access denied by default'); $test->expect(!$access->granted('GET /admin', 'client') && $access->granted('GET /admin', 'admin') && $access->granted('GET /admin', 'prod'), 'Access to a specific path granted to specific subjects'); $test->expect($access->granted('GET /admin/part2', 'admin'), 'Access to a subpath granted to a specific subject (subpath precedence)'); $test->expect(!$access->granted('GET /admin/part2', 'prod'), 'Access to a subpath denied to others (subpath precedence)'); //Wildcards $access = new \Access(); $access->policy('allow'); $access->deny('/admin*'); $access->allow('/admin*', 'admin'); $test->expect(!$access->granted('/admin') && !$access->granted('/admin/foo/bar') && $access->granted('/admin', 'admin') && $access->granted('/admin/foo/bar', 'admin'), 'Wildcard suffix'); $access->deny('/*/edit'); $access->allow('/*/edit', 'admin'); $test->expect(!$access->granted('/blog/entry/edit') && $access->granted('/blog/entry/edit', 'admin'), 'Wildcard prefix'); $access->allow('/admin'); $access->allow('/admin/special/path'); $test->expect($access->granted('/admin') && !$access->granted('/admin/foo/bar') && $access->granted('/admin', 'admin') && $access->granted('/admin/foo/bar', 'admin') && $access->granted('/admin/special/path') && $access->granted('/admin/special/path', 'admin'), 'Wildcard precedence order'); //Tokens $access = new \Access(); $access->deny('/@lang/foo'); $test->expect(!$access->granted('/en/foo') && $access->granted('/en/bar/foo'), 'Route tokens support'); $access->deny('/foo/@/baz'); $test->expect(!$access->granted('/foo/bar/baz') && $access->granted('/foo/bar/baz/bis'), 'Route tokens optional naming'); //Named routes $f3->route('GET @blog_entry:/blog/@id/@slug', 'Blog->Entry'); $access->deny('@blog_entry'); $test->expect(!$access->granted('/blog/1/hello') && $access->granted('/blog/1/hello/form') && $access->granted('/blog/1'), 'Named routes support'); //Verb-level control $access = new \Access(); $access->policy('allow'); $access->deny('POST|PUT|DELETE /blog/entry', '*'); $access->allow('* /blog/entry', 'admin'); $test->expect($access->granted('GET /blog/entry', 'client') && !$access->granted('PUT /blog/entry', 'client') && $access->granted('PUT /blog/entry', 'admin'), 'Verb-level access control'); //Multiple subjects $test->expect($access->granted('GET /blog/entry', array('client', 'customer')) && !$access->granted('PUT /blog/entry', array('client', 'customer')) && $access->granted('PUT /blog/entry', array('client', 'admin')), 'Check access for a set of subjects'); //Authorize method $f3->HALT = FALSE; $f3->VERB = 'GET'; $f3->PATH = '/blog/entry'; $f3->clear('ERROR'); $f3->ONERROR = function ($f3) { }; //do nothing $test->expect($access->authorize() && !$f3->get('ERROR.code'), 'Authorize an unidentified subject'); $f3->VERB = 'POST'; $f3->clear('ERROR'); $f3->ONERROR = function ($f3) { }; //do nothing $test->expect(!$access->authorize() && $f3->get('ERROR.code') == 401, 'Unauthorize an unidentified subject (401 error)'); $f3->clear('ERROR'); $f3->ONERROR = function ($f3) { }; //do nothing $test->expect($access->authorize('admin') && !$f3->get('ERROR.code'), 'Authorize an identified subject'); $f3->clear('ERROR'); $f3->ONERROR = function ($f3) { }; //do nothing $test->expect(!$access->authorize('client') && $f3->get('ERROR.code') == 403, 'Unauthorize an identified subject (403 error)'); $f3->clear('ERROR'); $f3->ONERROR = function ($f3) { }; //do nothing $test->expect($access->authorize(array('client', 'admin')) && !$f3->get('ERROR.code'), 'Authorize a set of identified subjects'); $f3->clear('ERROR'); $f3->ONERROR = function ($f3) { }; //do nothing $test->expect(!$access->authorize(array('client', 'customer')) && $f3->get('ERROR.code') == 403, 'Unauthorize a set of identified subjects'); //Config variable $f3->HALT = TRUE; $f3->ONERROR = NULL; $f3->set('ACCESS.policy', 'deny'); $f3->set('ACCESS.rules', array('ALLOW * /foo' => '*', 'DENY DELETE /foo' => '*', 'ALLOW DELETE /foo' => 'admin')); $access = new \Access(); $test->expect(!$access->granted('/') && !$access->granted('/', 'admin'), 'ACCESS.default config variable'); $test->expect($access->granted('GET /foo') && !$access->granted('DELETE /foo') && $access->granted('DELETE /foo', 'admin'), 'ACCESS.rules config variable'); $f3->set('results', $test->results()); }