diff --git a/lib/Database.php b/lib/Database.php index a75e89d..7c74eeb 100644 --- a/lib/Database.php +++ b/lib/Database.php @@ -8,10 +8,6 @@ use JKingWeb\Arsse\Misc\Date; class Database { const SCHEMA_VERSION = 1; - const FORMAT_TS = "Y-m-d h:i:s"; - const FORMAT_DATE = "Y-m-d"; - const FORMAT_TIME = "h:i:s"; - /** @var Db\Driver */ public $db; @@ -29,7 +25,7 @@ class Database { return debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]['function']; } - static public function listDrivers(): array { + static public function driverList(): array { $sep = \DIRECTORY_SEPARATOR; $path = __DIR__.$sep."Db".$sep; $classes = []; @@ -41,11 +37,11 @@ class Database { return $classes; } - public function schemaVersion(): int { + public function driverSchemaVersion(): int { return $this->db->schemaVersion(); } - public function schemaUpdate(): bool { + public function driverSchemaUpdate(): bool { if($this->db->schemaVersion() < self::SCHEMA_VERSION) return $this->db->schemaUpdate(self::SCHEMA_VERSION); return false; } @@ -86,17 +82,16 @@ class Database { return $this->db->prepare("SELECT value from arsse_meta where key is ?", "str")->run($key)->getValue(); } - public function metaSet(string $key, string $value, string $type = "str"): bool { - $out = !$this->db->prepare("UPDATE arsse_meta set value = ? where key is ?", $type, "str")->run($value, $key)->changes(); + public function metaSet(string $key, $value, string $type = "str"): bool { + $out = $this->db->prepare("UPDATE arsse_meta set value = ? where key is ?", $type, "str")->run($value, $key)->changes(); if(!$out) { - $out = $this->db->prepare("INSERT INTO arsse_meta(key,value)", "str", $type)->run($key, $value)->changes(); + $out = $this->db->prepare("INSERT INTO arsse_meta(key,value) values(?,?)", "str", $type)->run($key, $value)->changes(); } return (bool) $out; } public function metaRemove(string $key): bool { - $this->db->prepare("DELETE from arsse_meta where key is ?", "str")->run($key); - return true; + return (bool) $this->db->prepare("DELETE from arsse_meta where key is ?", "str")->run($key)->changes(); } public function userExists(string $user): bool { @@ -776,7 +771,7 @@ class Database { return (bool) $out; } - public function articleValidateId(string $user, int $id): array { + protected function articleValidateId(string $user, int $id): array { $out = $this->db->prepare( "SELECT arsse_articles.id as article, diff --git a/lib/Feed.php b/lib/Feed.php index ef2f7a0..3a6e2a0 100644 --- a/lib/Feed.php +++ b/lib/Feed.php @@ -52,7 +52,7 @@ class Feed { $this->nextFetch = $this->computeNextFetch(); } - public function download(string $url, string $lastModified = '', string $etag = '', string $username = '', string $password = ''): bool { + protected function download(string $url, string $lastModified = '', string $etag = '', string $username = '', string $password = ''): bool { try { $this->reader = new Reader($this->config); $this->resource = $this->reader->download($url, $lastModified, $etag, $username, $password); @@ -62,7 +62,7 @@ class Feed { return true; } - public function parse(): bool { + protected function parse(): bool { try { $this->parser = $this->reader->getParser( $this->resource->getUrl(), @@ -192,7 +192,7 @@ class Feed { return $out; } - public function matchToDatabase(int $feedID = null): bool { + protected function matchToDatabase(int $feedID = null): bool { // first perform deduplication on items $items = $this->deduplicateItems($this->data->items); // if we haven't been given a database feed ID to check against, all items are new @@ -221,7 +221,7 @@ class Feed { return true; } - public function matchItems(array $items, array $articles): array { + protected function matchItems(array $items, array $articles): array { $new = $edited = []; // iterate through the articles and for each determine whether it is existing, edited, or entirely new foreach($items as $i) { @@ -260,7 +260,7 @@ class Feed { return [$new, $edited]; } - public function computeNextFetch(): \DateTime { + protected function computeNextFetch(): \DateTime { $now = Date::normalize(time()); if(!$this->modified) { $diff = $now->getTimestamp() - $this->lastModified->getTimestamp(); @@ -318,7 +318,7 @@ class Feed { return $offset; } - public function computeLastModified() { + protected function computeLastModified() { if(!$this->modified) return $this->lastModified; $dates = $this->gatherDates(); if(sizeof($dates)) { diff --git a/lib/Service.php b/lib/Service.php index 268151c..2fed9a1 100644 --- a/lib/Service.php +++ b/lib/Service.php @@ -9,6 +9,18 @@ class Service { protected $drv; /** @var \DateInterval */ protected $interval; + + static public function driverList(): array { + $sep = \DIRECTORY_SEPARATOR; + $path = __DIR__.$sep."Service".$sep; + $classes = []; + foreach(glob($path."*".$sep."Driver.php") as $file) { + $name = basename(dirname($file)); + $class = NS_BASE."User\\$name\\Driver"; + $classes[$class] = $class::driverName(); + } + return $classes; + } protected static function interval(): \DateInterval { return new \DateInterval(Arsse::$conf->serviceFrequency); // FIXME: this needs to fall back in case of incorrect input @@ -32,6 +44,7 @@ class Service { $this->drv->exec(); $this->drv->clean(); static::cleanupPost(); + unset($list); } $t->add($this->interval); do { diff --git a/lib/User.php b/lib/User.php index 759430e..13ab49f 100644 --- a/lib/User.php +++ b/lib/User.php @@ -19,7 +19,7 @@ class User { protected $authzSupported = 0; protected $actor = []; - static public function listDrivers(): array { + static public function driverList(): array { $sep = \DIRECTORY_SEPARATOR; $path = __DIR__.$sep."User".$sep; $classes = []; diff --git a/tests/Db/SQLite3/Database/TestDatabaseMetaSQLite3.php b/tests/Db/SQLite3/Database/TestDatabaseMetaSQLite3.php new file mode 100644 index 0000000..3356eec --- /dev/null +++ b/tests/Db/SQLite3/Database/TestDatabaseMetaSQLite3.php @@ -0,0 +1,9 @@ + [ + 'columns' => [ + 'key' => 'str', + 'value' => 'str', + ], + 'rows' => [ + //['schema_version', "".\JKingWeb\Arsse\Database::SCHEMA_VERSION], + ['album',"A Farewell to Kings"], + ], + ], + ]; + + function setUpSeries() { + // the schema_version key is a special case, and to avoid jumping through hoops for every test we deal with it now + $this->data = $this->dataBare; + // as far as tests are concerned the schema version is part of the expectations primed into the database + array_unshift($this->data['arsse_meta']['rows'], ['schema_version', "".Database::SCHEMA_VERSION]); + // but it's already been inserted by the driver, so we prime without it + $this->primeDatabase($this->dataBare); + } + + function testAddANewValue() { + $this->assertTrue(Arsse::$db->metaSet("favourite", "Cygnus X-1")); + $state = $this->primeExpectations($this->data, ['arsse_meta' => ['key','value']]); + $state['arsse_meta']['rows'][] = ["favourite","Cygnus X-1"]; + $this->compareExpectations($state); + } + + function testAddANewTypedValue() { + $this->assertTrue(Arsse::$db->metaSet("answer", 42, "int")); + $this->assertTrue(Arsse::$db->metaSet("true", true, "bool")); + $this->assertTrue(Arsse::$db->metaSet("false", false, "bool")); + $this->assertTrue(Arsse::$db->metaSet("millennium", new \DateTime("2000-01-01T00:00:00Z"), "datetime")); + $state = $this->primeExpectations($this->data, ['arsse_meta' => ['key','value']]); + $state['arsse_meta']['rows'][] = ["answer","42"]; + $state['arsse_meta']['rows'][] = ["true","1"]; + $state['arsse_meta']['rows'][] = ["false","0"]; + $state['arsse_meta']['rows'][] = ["millennium","2000-01-01 00:00:00"]; + $this->compareExpectations($state); + } + + function testChangeAnExistingValue() { + $this->assertTrue(Arsse::$db->metaSet("album", "Hemispheres")); + $state = $this->primeExpectations($this->data, ['arsse_meta' => ['key','value']]); + $state['arsse_meta']['rows'][1][1] = "Hemispheres"; + $this->compareExpectations($state); + } + + function testRemoveAValue() { + $this->assertTrue(Arsse::$db->metaRemove("album")); + $this->assertFalse(Arsse::$db->metaRemove("album")); + $state = $this->primeExpectations($this->data, ['arsse_meta' => ['key','value']]); + unset($state['arsse_meta']['rows'][1]); + $this->compareExpectations($state); + } + + function testRetrieveAValue() { + $this->assertSame("".Database::SCHEMA_VERSION, Arsse::$db->metaGet("schema_version")); + $this->assertSame("A Farewell to Kings", Arsse::$db->metaGet("album")); + $this->assertSame(null, Arsse::$db->metaGet("this_key_does_not_exist")); + } +} \ No newline at end of file diff --git a/tests/lib/Database/Setup.php b/tests/lib/Database/Setup.php index 3e406ce..57484fc 100644 --- a/tests/lib/Database/Setup.php +++ b/tests/lib/Database/Setup.php @@ -11,6 +11,7 @@ use Phake; trait Setup { protected $drv; + protected $primed = false; function setUp() { // establish a clean baseline @@ -21,20 +22,21 @@ trait Setup { $this->setUpDriver(); // create the database interface with the suitable driver Arsse::$db = new Database($this->drv); - Arsse::$db->schemaUpdate(); + Arsse::$db->driverSchemaUpdate(); // create a mock user manager Arsse::$user = Phake::mock(User::class); Phake::when(Arsse::$user)->authorize->thenReturn(true); // call the additional setup method if it exists if(method_exists($this, "setUpSeries")) $this->setUpSeries(); - // prime the database with series data - if(isset($this->data)) $this->primeDatabase($this->data); + // prime the database with series data if it hasn't already been done + if(!$this->primed && isset($this->data)) $this->primeDatabase($this->data); } function tearDown() { // call the additional teardiwn method if it exists if(method_exists($this, "tearDownSeries")) $this->tearDownSeries(); // clean up + $this->primed = false; $this->drv = null; $this->clearData(); } @@ -51,6 +53,7 @@ trait Setup { } } $tr->commit(); + $this->primed = true; return true; } diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 27b7b20..04178c4 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -42,6 +42,7 @@ Db/SQLite3/TestDbUpdateSQLite3.php + Db/SQLite3/Database/TestDatabaseMetaSQLite3.php Db/SQLite3/Database/TestDatabaseUserSQLite3.php Db/SQLite3/Database/TestDatabaseFolderSQLite3.php Db/SQLite3/Database/TestDatabaseFeedSQLite3.php