From ca74886f30da323f42aa4bd70461003f46ef299b Mon Sep 17 00:00:00 2001 From: VirtualTam Date: Thu, 12 Mar 2015 00:43:02 +0100 Subject: LinkDB: move to a proper file, add test coverage Relates to #71 LinkDB - move to application/LinkDB.php - code cleanup - indentation - whitespaces - formatting - comment cleanup - add missing documentation - unify formatting Test coverage for LinkDB - constructor - public / private access - link-related methods Shaarli utilities (LinkDB dependencies) - move startsWith() and endsWith() functions to application/Utils.php - add test coverage Dev utilities - Composer: add PHPUnit to dev dependencies - Makefile: - update lint targets - add test targets - generate coverage reports Signed-off-by: VirtualTam --- tests/.htaccess | 2 + tests/LinkDBTest.php | 509 ++++++++++++++++++++++++++++++++++++++++ tests/UtilsTest.php | 78 ++++++ tests/utils/ReferenceLinkDB.php | 128 ++++++++++ 4 files changed, 717 insertions(+) create mode 100644 tests/.htaccess create mode 100644 tests/LinkDBTest.php create mode 100644 tests/UtilsTest.php create mode 100644 tests/utils/ReferenceLinkDB.php (limited to 'tests') diff --git a/tests/.htaccess b/tests/.htaccess new file mode 100644 index 00000000..b584d98c --- /dev/null +++ b/tests/.htaccess @@ -0,0 +1,2 @@ +Allow from none +Deny from all diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php new file mode 100644 index 00000000..bbe4e026 --- /dev/null +++ b/tests/LinkDBTest.php @@ -0,0 +1,509 @@ +'); + + +/** + * Unitary tests for LinkDB + */ +class LinkDBTest extends PHPUnit_Framework_TestCase +{ + // datastore to test write operations + protected static $testDatastore = 'tests/datastore.php'; + protected static $dummyDatastoreSHA1 = 'e3edea8ea7bb50be4bcb404df53fbb4546a7156e'; + protected static $refDB = null; + protected static $publicLinkDB = null; + protected static $privateLinkDB = null; + + /** + * Instantiates public and private LinkDBs with test data + * + * The reference datastore contains public and private links that + * will be used to test LinkDB's methods: + * - access filtering (public/private), + * - link searches: + * - by day, + * - by tag, + * - by text, + * - etc. + */ + public static function setUpBeforeClass() + { + self::$refDB = new ReferenceLinkDB(); + self::$refDB->write(self::$testDatastore, PHPPREFIX, PHPSUFFIX); + + $GLOBALS['config']['DATASTORE'] = self::$testDatastore; + self::$publicLinkDB = new LinkDB(false); + self::$privateLinkDB = new LinkDB(true); + } + + /** + * Resets test data for each test + */ + protected function setUp() + { + $GLOBALS['config']['DATASTORE'] = self::$testDatastore; + if (file_exists(self::$testDatastore)) { + unlink(self::$testDatastore); + } + } + + /** + * Allows to test LinkDB's private methods + * + * @see + * https://sebastian-bergmann.de/archives/881-Testing-Your-Privates.html + * http://stackoverflow.com/a/2798203 + */ + protected static function getMethod($name) + { + $class = new ReflectionClass('LinkDB'); + $method = $class->getMethod($name); + $method->setAccessible(true); + return $method; + } + + /** + * Instantiate LinkDB objects - logged in user + */ + public function testConstructLoggedIn() + { + new LinkDB(true); + $this->assertFileExists(self::$testDatastore); + } + + /** + * Instantiate LinkDB objects - logged out or public instance + */ + public function testConstructLoggedOut() + { + new LinkDB(false); + $this->assertFileExists(self::$testDatastore); + } + + /** + * Attempt to instantiate a LinkDB whereas the datastore is not writable + * + * @expectedException PHPUnit_Framework_Error_Warning + * @expectedExceptionMessageRegExp /failed to open stream: No such file or directory/ + */ + public function testConstructDatastoreNotWriteable() + { + $GLOBALS['config']['DATASTORE'] = 'null/store.db'; + new LinkDB(false); + } + + /** + * The DB doesn't exist, ensure it is created with dummy content + */ + public function testCheckDBNew() + { + $linkDB = new LinkDB(false); + unlink(self::$testDatastore); + $this->assertFileNotExists(self::$testDatastore); + + $checkDB = self::getMethod('checkDB'); + $checkDB->invokeArgs($linkDB, array()); + $this->assertFileExists(self::$testDatastore); + + // ensure the correct data has been written + $this->assertEquals( + self::$dummyDatastoreSHA1, + sha1_file(self::$testDatastore) + ); + } + + /** + * The DB exists, don't do anything + */ + public function testCheckDBLoad() + { + $linkDB = new LinkDB(false); + $this->assertEquals( + self::$dummyDatastoreSHA1, + sha1_file(self::$testDatastore) + ); + + $checkDB = self::getMethod('checkDB'); + $checkDB->invokeArgs($linkDB, array()); + + // ensure the datastore is left unmodified + $this->assertEquals( + self::$dummyDatastoreSHA1, + sha1_file(self::$testDatastore) + ); + } + + /** + * Load an empty DB + */ + public function testReadEmptyDB() + { + file_put_contents(self::$testDatastore, PHPPREFIX.'S7QysKquBQA='.PHPSUFFIX); + $emptyDB = new LinkDB(false); + $this->assertEquals(0, sizeof($emptyDB)); + $this->assertEquals(0, count($emptyDB)); + } + + /** + * Load public links from the DB + */ + public function testReadPublicDB() + { + $this->assertEquals( + self::$refDB->countPublicLinks(), + sizeof(self::$publicLinkDB) + ); + } + + /** + * Load public and private links from the DB + */ + public function testReadPrivateDB() + { + $this->assertEquals( + self::$refDB->countLinks(), + sizeof(self::$privateLinkDB) + ); + } + + /** + * Save the links to the DB + */ + public function testSaveDB() + { + $testDB = new LinkDB(true); + $dbSize = sizeof($testDB); + + $link = array( + 'title'=>'an additional link', + 'url'=>'http://dum.my', + 'description'=>'One more', + 'private'=>0, + 'linkdate'=>'20150518_190000', + 'tags'=>'unit test' + ); + $testDB[$link['linkdate']] = $link; + + // TODO: move PageCache to a proper class/file + function invalidateCaches() {} + + $testDB->savedb(); + + $testDB = new LinkDB(true); + $this->assertEquals($dbSize + 1, sizeof($testDB)); + } + + /** + * Count existing links + */ + public function testCount() + { + $this->assertEquals( + self::$refDB->countPublicLinks(), + self::$publicLinkDB->count() + ); + $this->assertEquals( + self::$refDB->countLinks(), + self::$privateLinkDB->count() + ); + } + + /** + * List the days for which links have been posted + */ + public function testDays() + { + $this->assertEquals( + ['20121206', '20130614', '20150310'], + self::$publicLinkDB->days() + ); + + $this->assertEquals( + ['20121206', '20130614', '20141125', '20150310'], + self::$privateLinkDB->days() + ); + } + + /** + * The URL corresponds to an existing entry in the DB + */ + public function testGetKnownLinkFromURL() + { + $link = self::$publicLinkDB->getLinkFromUrl('http://mediagoblin.org/'); + + $this->assertNotEquals(false, $link); + $this->assertEquals( + 'A free software media publishing platform', + $link['description'] + ); + } + + /** + * The URL is not in the DB + */ + public function testGetUnknownLinkFromURL() + { + $this->assertEquals( + false, + self::$publicLinkDB->getLinkFromUrl('http://dev.null') + ); + } + + /** + * Lists all tags + */ + public function testAllTags() + { + $this->assertEquals( + [ + 'web' => 3, + 'cartoon' => 2, + 'gnu' => 2, + 'dev' => 1, + 'samba' => 1, + 'media' => 1, + 'software' => 1, + 'stallman' => 1, + 'free' => 1 + ], + self::$publicLinkDB->allTags() + ); + + $this->assertEquals( + [ + 'web' => 4, + 'cartoon' => 3, + 'gnu' => 2, + 'dev' => 2, + 'samba' => 1, + 'media' => 1, + 'software' => 1, + 'stallman' => 1, + 'free' => 1, + 'html' => 1, + 'w3c' => 1, + 'css' => 1, + 'Mercurial' => 1 + ], + self::$privateLinkDB->allTags() + ); + } + + /** + * Filter links using a tag + */ + public function testFilterOneTag() + { + $this->assertEquals( + 3, + sizeof(self::$publicLinkDB->filterTags('web', false)) + ); + + $this->assertEquals( + 4, + sizeof(self::$privateLinkDB->filterTags('web', false)) + ); + } + + /** + * Filter links using a tag - case-sensitive + */ + public function testFilterCaseSensitiveTag() + { + $this->assertEquals( + 0, + sizeof(self::$privateLinkDB->filterTags('mercurial', true)) + ); + + $this->assertEquals( + 1, + sizeof(self::$privateLinkDB->filterTags('Mercurial', true)) + ); + } + + /** + * Filter links using a tag combination + */ + public function testFilterMultipleTags() + { + $this->assertEquals( + 1, + sizeof(self::$publicLinkDB->filterTags('dev cartoon', false)) + ); + + $this->assertEquals( + 2, + sizeof(self::$privateLinkDB->filterTags('dev cartoon', false)) + ); + } + + /** + * Filter links using a non-existent tag + */ + public function testFilterUnknownTag() + { + $this->assertEquals( + 0, + sizeof(self::$publicLinkDB->filterTags('null', false)) + ); + } + + /** + * Return links for a given day + */ + public function testFilterDay() + { + $this->assertEquals( + 2, + sizeof(self::$publicLinkDB->filterDay('20121206')) + ); + + $this->assertEquals( + 3, + sizeof(self::$privateLinkDB->filterDay('20121206')) + ); + } + + /** + * 404 - day not found + */ + public function testFilterUnknownDay() + { + $this->assertEquals( + 0, + sizeof(self::$publicLinkDB->filterDay('19700101')) + ); + + $this->assertEquals( + 0, + sizeof(self::$privateLinkDB->filterDay('19700101')) + ); + } + + /** + * Use an invalid date format + */ + public function testFilterInvalidDay() + { + $this->assertEquals( + 0, + sizeof(self::$privateLinkDB->filterDay('Rainy day, dream away')) + ); + + // TODO: check input format + $this->assertEquals( + 6, + sizeof(self::$privateLinkDB->filterDay('20')) + ); + } + + /** + * Retrieve a link entry with its hash + */ + public function testFilterSmallHash() + { + $links = self::$privateLinkDB->filterSmallHash('IuWvgA'); + + $this->assertEquals( + 1, + sizeof($links) + ); + + $this->assertEquals( + 'MediaGoblin', + $links['20130614_184135']['title'] + ); + + } + + /** + * No link for this hash + */ + public function testFilterUnknownSmallHash() + { + $this->assertEquals( + 0, + sizeof(self::$privateLinkDB->filterSmallHash('Iblaah')) + ); + } + + /** + * Full-text search - result from a link's URL + */ + public function testFilterFullTextURL() + { + $this->assertEquals( + 2, + sizeof(self::$publicLinkDB->filterFullText('ars.userfriendly.org')) + ); + } + + /** + * Full-text search - result from a link's title only + */ + public function testFilterFullTextTitle() + { + // use miscellaneous cases + $this->assertEquals( + 2, + sizeof(self::$publicLinkDB->filterFullText('userfriendly -')) + ); + $this->assertEquals( + 2, + sizeof(self::$publicLinkDB->filterFullText('UserFriendly -')) + ); + $this->assertEquals( + 2, + sizeof(self::$publicLinkDB->filterFullText('uSeRFrIendlY -')) + ); + + // use miscellaneous case and offset + $this->assertEquals( + 2, + sizeof(self::$publicLinkDB->filterFullText('RFrIendL')) + ); + } + + /** + * Full-text search - result from the link's description only + */ + public function testFilterFullTextDescription() + { + $this->assertEquals( + 1, + sizeof(self::$publicLinkDB->filterFullText('media publishing')) + ); + } + + /** + * Full-text search - result from the link's tags only + */ + public function testFilterFullTextTags() + { + $this->assertEquals( + 2, + sizeof(self::$publicLinkDB->filterFullText('gnu')) + ); + } + + /** + * Full-text search - result set from mixed sources + */ + public function testFilterFullTextMixed() + { + $this->assertEquals( + 2, + sizeof(self::$publicLinkDB->filterFullText('free software')) + ); + } +} +?> diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php new file mode 100644 index 00000000..bbba99f2 --- /dev/null +++ b/tests/UtilsTest.php @@ -0,0 +1,78 @@ +assertEquals('CyAAJw', smallHash('http://test.io')); + $this->assertEquals(6, strlen(smallHash('https://github.com'))); + } + + /** + * Look for a substring at the beginning of a string + */ + public function testStartsWithCaseInsensitive() + { + $this->assertTrue(startsWith('Lorem ipsum', 'lorem', false)); + $this->assertTrue(startsWith('Lorem ipsum', 'LoReM i', false)); + } + + /** + * Look for a substring at the beginning of a string (case-sensitive) + */ + public function testStartsWithCaseSensitive() + { + $this->assertTrue(startsWith('Lorem ipsum', 'Lorem', true)); + $this->assertFalse(startsWith('Lorem ipsum', 'lorem', true)); + $this->assertFalse(startsWith('Lorem ipsum', 'LoReM i', true)); + } + + /** + * Look for a substring at the beginning of a string (Unicode) + */ + public function testStartsWithSpecialChars() + { + $this->assertTrue(startsWith('å!ùµ', 'å!', false)); + $this->assertTrue(startsWith('µ$åù', 'µ$', true)); + } + + /** + * Look for a substring at the end of a string + */ + public function testEndsWithCaseInsensitive() + { + $this->assertTrue(endsWith('Lorem ipsum', 'ipsum', false)); + $this->assertTrue(endsWith('Lorem ipsum', 'm IpsUM', false)); + } + + /** + * Look for a substring at the end of a string (case-sensitive) + */ + public function testEndsWithCaseSensitive() + { + $this->assertTrue(endsWith('lorem Ipsum', 'Ipsum', true)); + $this->assertFalse(endsWith('lorem Ipsum', 'ipsum', true)); + $this->assertFalse(endsWith('lorem Ipsum', 'M IPsuM', true)); + } + + /** + * Look for a substring at the end of a string (Unicode) + */ + public function testEndsWithSpecialChars() + { + $this->assertTrue(endsWith('å!ùµ', 'ùµ', false)); + $this->assertTrue(endsWith('µ$åù', 'åù', true)); + } +} +?> diff --git a/tests/utils/ReferenceLinkDB.php b/tests/utils/ReferenceLinkDB.php new file mode 100644 index 00000000..2cb05bae --- /dev/null +++ b/tests/utils/ReferenceLinkDB.php @@ -0,0 +1,128 @@ +addLink( + 'Free as in Freedom 2.0', + 'https://static.fsf.org/nosvn/faif-2.0.pdf', + 'Richard Stallman and the Free Software Revolution', + 0, + '20150310_114633', + 'free gnu software stallman' + ); + + $this->addLink( + 'MediaGoblin', + 'http://mediagoblin.org/', + 'A free software media publishing platform', + 0, + '20130614_184135', + 'gnu media web' + ); + + $this->addLink( + 'w3c-markup-validator', + 'https://dvcs.w3.org/hg/markup-validator/summary', + 'Mercurial repository for the W3C Validator', + 1, + '20141125_084734', + 'css html w3c web Mercurial' + ); + + $this->addLink( + 'UserFriendly - Web Designer', + 'http://ars.userfriendly.org/cartoons/?id=20121206', + 'Naming conventions...', + 0, + '20121206_142300', + 'dev cartoon web' + ); + + $this->addLink( + 'UserFriendly - Samba', + 'http://ars.userfriendly.org/cartoons/?id=20010306', + 'Tropical printing', + 0, + '20121206_172539', + 'samba cartoon web' + ); + + $this->addLink( + 'Geek and Poke', + 'http://geek-and-poke.com/', + '', + 1, + '20121206_182539', + 'dev cartoon' + ); + } + + /** + * Adds a new link + */ + protected function addLink($title, $url, $description, $private, $date, $tags) + { + $link = array( + 'title' => $title, + 'url' => $url, + 'description' => $description, + 'private' => $private, + 'linkdate' => $date, + 'tags' => $tags, + ); + $this->links[$date] = $link; + + if ($private) { + $this->privateCount++; + return; + } + $this->publicCount++; + } + + /** + * Writes data to the datastore + */ + public function write($filename, $prefix, $suffix) + { + file_put_contents( + $filename, + $prefix.base64_encode(gzdeflate(serialize($this->links))).$suffix + ); + } + + /** + * Returns the number of links in the reference data + */ + public function countLinks() + { + return $this->publicCount + $this->privateCount; + } + + /** + * Returns the number of public links in the reference data + */ + public function countPublicLinks() + { + return $this->publicCount; + } + + /** + * Returns the number of private links in the reference data + */ + public function countPrivateLinks() + { + return $this->privateCount; + } +} +?> -- cgit v1.2.3