diff --git a/lib/Database.php b/lib/Database.php index 19deb8fa..9bc44afc 100644 --- a/lib/Database.php +++ b/lib/Database.php @@ -323,7 +323,7 @@ class Database { throw new User\Exception("doesNotExist", ["user" => $user, "action" => __FUNCTION__]); } // if the desired folder name is missing or invalid, throw an exception - if(!array_key_exists("name", $data)) { + if(!array_key_exists("name", $data) || $data['name']=="") { throw new Db\ExceptionInput("missing", ["action" => __FUNCTION__, "field" => "name"]); } else if(!strlen(trim($data['name']))) { throw new Db\ExceptionInput("whitespace", ["action" => __FUNCTION__, "field" => "name"]); diff --git a/sql/SQLite3/0.sql b/sql/SQLite3/0.sql index 0b5f64f7..18d0ab82 100644 --- a/sql/SQLite3/0.sql +++ b/sql/SQLite3/0.sql @@ -38,21 +38,21 @@ create table arsse_feeds( -- users' subscriptions to newsfeeds, with settings create table arsse_subscriptions( id integer primary key not null, -- sequence number - owner TEXT not null references arsse_users(id) on delete cascade on update cascade, -- owner of subscription - feed integer not null references arsse_feeds(id) on delete cascade, -- feed for the subscription + owner TEXT not null references arsse_users(id) on delete cascade on update cascade, -- owner of subscription + feed integer not null references arsse_feeds(id) on delete cascade, -- feed for the subscription added datetime not null default CURRENT_TIMESTAMP, -- time at which feed was added modified datetime not null default CURRENT_TIMESTAMP, -- date at which subscription properties were last modified title TEXT, -- user-supplied title order_type int not null default 0, -- NextCloud sort order pinned boolean not null default 0, -- whether feed is pinned (always sorts at top) - folder integer references arsse_folders(id) on delete cascade, -- TT-RSS category (nestable); the first-level category (which acts as NextCloud folder) is joined in when needed + folder integer references arsse_folders(id) on delete cascade, -- TT-RSS category (nestable); the first-level category (which acts as NextCloud folder) is joined in when needed unique(owner,feed) -- a given feed should only appear once for a given owner ); -- TT-RSS categories and NextCloud folders create table arsse_folders( id integer primary key not null, -- sequence number - owner TEXT not null references arsse_users(id) on delete cascade on update cascade, -- owner of folder + owner TEXT not null references arsse_users(id) on delete cascade on update cascade, -- owner of folder parent integer default null, -- parent folder id root integer default null, -- first-level folder (NextCloud folder) name TEXT not null, -- folder name @@ -63,7 +63,7 @@ create table arsse_folders( -- entries in newsfeeds create table arsse_articles( id integer primary key not null, -- sequence number - feed integer not null references arsse_feeds(id) on delete cascade, -- feed for the subscription + feed integer not null references arsse_feeds(id) on delete cascade, -- feed for the subscription url TEXT not null, -- URL of article title TEXT, -- article title author TEXT, -- author's name diff --git a/tests/lib/Database/SeriesFolder.php b/tests/lib/Database/SeriesFolder.php new file mode 100644 index 00000000..d7cea89d --- /dev/null +++ b/tests/lib/Database/SeriesFolder.php @@ -0,0 +1,102 @@ + [ + 'columns' => [ + 'id' => "int", + 'owner' => "str", + 'parent' => "int", + 'root' => "int", + 'name' => "str", + ], + /* Layout translates to: + Jane + Politics + John + Technology + Software + Politics + Rocketry + Politics + */ + 'rows' => [ + [1, "john.doe@example.com", null, null, "Technology"], + [2, "john.doe@example.com", 1, 1, "Software"], + [3, "john.doe@example.com", 1, 1, "Rocketry"], + [4, "jane.doe@example.com", null, null, "Politics"], + [5, "john.doe@example.com", null, null, "Politics"], + [6, "john.doe@example.com", 2, 1, "Politics"], + ] + ] + ]; + // merge folder table with base user table + $this->data = array_merge($this->data, $data); + $this->primeDatabase($this->data); + } + + function testAddARootFolder() { + $user = "john.doe@example.com"; + $this->assertSame(7, Data::$db->folderAdd($user, ['name' => "Entertainment"])); + Phake::verify(Data::$user)->authorize($user, "folderAdd"); + $state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'root', 'name']]); + $state['arsse_folders']['rows'][] = [7, $user, null, null, "Entertainment"]; + $this->compareExpectations($state); + } + + function testAddADuplicateRootFolder() { + $this->assertException("constraintViolation", "Db", "ExceptionInput"); + Data::$db->folderAdd("john.doe@example.com", ['name' => "Politics"]); + } + + function testAddANestedFolder() { + $user = "john.doe@example.com"; + $this->assertSame(7, Data::$db->folderAdd($user, ['name' => "GNOME", 'parent' => 2])); + Phake::verify(Data::$user)->authorize($user, "folderAdd"); + $state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'root', 'name']]); + $state['arsse_folders']['rows'][] = [7, $user, 2, 1, "GNOME"]; + $this->compareExpectations($state); + } + + function testAddANestedFolderToAMissingParent() { + $this->assertException("idMissing", "Db", "ExceptionInput"); + Data::$db->folderAdd("john.doe@example.com", ['name' => "Sociology", 'parent' => 2112]); + } + + function testAddANestedFolderForTheWrongOwner() { + $this->assertException("idMissing", "Db", "ExceptionInput"); + Data::$db->folderAdd("john.doe@example.com", ['name' => "Sociology", 'parent' => 4]); // folder ID 4 belongs to Jane + } + + function testAddAFolderForAMissingUser() { + $this->assertException("doesNotExist", "User"); + Data::$db->folderAdd("john.doe@example.org", ['name' => "Sociology"]); + } + + function testAddAFolderWithAMissingName() { + $this->assertException("missing", "Db", "ExceptionInput"); + Data::$db->folderAdd("john.doe@example.com", []); + } + + function testAddAFolderWithABlankName() { + $this->assertException("missing", "Db", "ExceptionInput"); + Data::$db->folderAdd("john.doe@example.com", ['name' => ""]); + } + + function testAddAFolderWithAWhitespaceName() { + $this->assertException("whitespace", "Db", "ExceptionInput"); + Data::$db->folderAdd("john.doe@example.com", ['name' => " "]); + } + + function testAddAFolderWithoutAuthority() { + Phake::when(Data::$user)->authorize->thenReturn(false); + $this->assertException("notAuthorized", "User", "ExceptionAuthz"); + Data::$db->folderAdd("john.doe@example.com", ['name' => "Sociology"]); + } +} \ No newline at end of file diff --git a/tests/lib/Database/Setup.php b/tests/lib/Database/Setup.php index f4d71f23..472b803c 100644 --- a/tests/lib/Database/Setup.php +++ b/tests/lib/Database/Setup.php @@ -71,7 +71,9 @@ trait Setup { $cols = implode(",", array_keys($info['columns'])); foreach($this->drv->prepare("SELECT $cols from $table")->run() as $num => $row) { $row = array_values($row); - $this->assertSame($expected[$table]['rows'][$num], $row, "Row ".($num+1)." of table $table does not match expectations at array index $num."); + $this->assertGreaterThan(0, sizeof($info['rows']), "Expectations contain fewer rows than the database table $table"); + $exp = array_shift($info['rows']); + $this->assertSame($exp, $row, "Row ".($num+1)." of table $table does not match expectations at array index $num."); } } return true; diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 2881db88..dbd3a1b0 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -10,40 +10,36 @@ beStrictAboutTestSize="true" stopOnError="true"> - - Exception/TestException.php - - - - Lang/TestLang.php - Lang/TestLangComplex.php - Lang/TestLangErrors.php - - - - Conf/TestConf.php - - - - User/TestUserMockInternal.php - User/TestUserMockExternal.php - User/TestUserInternalDriver.php - User/TestAuthorization.php - - - - Db/SQLite3/TestDbResultSQLite3.php - Db/SQLite3/TestDbStatementSQLite3.php - Db/SQLite3/TestDbDriverSQLite3.php - Db/SQLite3/TestDbUpdateSQLite3.php - - - - Db/SQLite3/Database/TestDatabaseUserSQLite3.php - - - - REST/NextCloudNews/TestNCNVersionDiscovery.php - - + + + Exception/TestException.php + + + Lang/TestLang.php + Lang/TestLangComplex.php + Lang/TestLangErrors.php + + + Conf/TestConf.php + + + User/TestUserMockInternal.php + User/TestUserMockExternal.php + User/TestUserInternalDriver.php + User/TestAuthorization.php + + + Db/SQLite3/TestDbResultSQLite3.php + Db/SQLite3/TestDbStatementSQLite3.php + Db/SQLite3/TestDbDriverSQLite3.php + Db/SQLite3/TestDbUpdateSQLite3.php + + + Db/SQLite3/Database/TestDatabaseUserSQLite3.php + Db/SQLite3/Database/TestDatabaseFolderSQLite3.php + + + REST/NextCloudNews/TestNCNVersionDiscovery.php + + \ No newline at end of file