diff --git a/lib/Database.php b/lib/Database.php index 6732ecab..6844541e 100644 --- a/lib/Database.php +++ b/lib/Database.php @@ -1275,7 +1275,7 @@ class Database { if (!Arsse::$user->authorize($user, __FUNCTION__)) { throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); } - return $this->db->prepare("SELECT i.id, i.url, i.type, i.data from arsse_icons as i join arsse_feeds as f on i.id = f.icon join arsse_subscriptions as s on s.feed = f.id where s.owner = ?", "str")->run($user); + return $this->db->prepare("SELECT distinct i.id, i.url, i.type, i.data from arsse_icons as i join arsse_feeds as f on i.id = f.icon join arsse_subscriptions as s on s.feed = f.id where s.owner = ?", "str")->run($user); } /** Deletes orphaned icons from the database diff --git a/lib/Service.php b/lib/Service.php index 597421fd..a69b12c0 100644 --- a/lib/Service.php +++ b/lib/Service.php @@ -72,6 +72,8 @@ class Service { public static function cleanupPre(): bool { // mark unsubscribed feeds as orphaned and delete orphaned feeds that are beyond their retention period Arsse::$db->feedCleanup(); + // do the same for icons + Arsse::$db->iconCleanup(); // delete expired log-in sessions Arsse::$db->sessionCleanup(); return true; diff --git a/tests/cases/Database/SeriesCleanup.php b/tests/cases/Database/SeriesCleanup.php index b31f87c7..ad1d7f14 100644 --- a/tests/cases/Database/SeriesCleanup.php +++ b/tests/cases/Database/SeriesCleanup.php @@ -66,6 +66,19 @@ trait SeriesCleanup { ["da772f8fa13c11e78667001e673b2560", "class.class", "john.doe@example.com", $soon], ], ], + 'arsse_icons' => [ + 'columns' => [ + 'id' => "int", + 'url' => "str", + 'orphaned' => "datetime", + ], + 'rows' => [ + [1,'http://localhost:8000/Icon/PNG',null], + [2,'http://localhost:8000/Icon/GIF',null], + [3,'http://localhost:8000/Icon/SVG1',null], + [4,'http://localhost:8000/Icon/SVG2',null], + ], + ], 'arsse_feeds' => [ 'columns' => [ 'id' => "int", diff --git a/tests/cases/Database/SeriesIcon.php b/tests/cases/Database/SeriesIcon.php index 7a7e348c..d54a4ab9 100644 --- a/tests/cases/Database/SeriesIcon.php +++ b/tests/cases/Database/SeriesIcon.php @@ -53,16 +53,11 @@ trait SeriesIcon { 'icon' => "int", ], 'rows' => [ - [1,"http://localhost:8000/Feed/Matching/3","Ook",0,"",$past,$past,0,null], - [2,"http://localhost:8000/Feed/Matching/1","Eek",5,"There was an error last time",$past,$future,0,null], - [3,"http://localhost:8000/Feed/Fetching/Error?code=404","Ack",0,"",$past,$now,0,null], + [1,"http://localhost:8000/Feed/Matching/3","Ook",0,"",$past,$past,0,1], + [2,"http://localhost:8000/Feed/Matching/1","Eek",5,"There was an error last time",$past,$future,0,2], + [3,"http://localhost:8000/Feed/Fetching/Error?code=404","Ack",0,"",$past,$now,0,3], [4,"http://localhost:8000/Feed/NextFetch/NotModified?t=".time(),"Ooook",0,"",$past,$past,0,null], - [5,"http://localhost:8000/Feed/Parsing/Valid","Ooook",0,"",$past,$future,0,null], - // these feeds all test icon caching - [6,"http://localhost:8000/Feed/WithIcon/PNG",null,0,"",$past,$future,0,1], // no change when updated - [7,"http://localhost:8000/Feed/WithIcon/GIF",null,0,"",$past,$future,0,1], // icon ID 2 will be assigned to feed when updated - [8,"http://localhost:8000/Feed/WithIcon/SVG1",null,0,"",$past,$future,0,3], // icon ID 3 will be modified when updated - [9,"http://localhost:8000/Feed/WithIcon/SVG2",null,0,"",$past,$future,0,null], // icon ID 4 will be created and assigned to feed when updated + [5,"http://localhost:8000/Feed/Parsing/Valid","Ooook",0,"",$past,$future,0,2], ], ], 'arsse_subscriptions' => [ @@ -77,7 +72,7 @@ trait SeriesIcon { [3,'john.doe@example.com',3], [4,'john.doe@example.com',4], [5,'john.doe@example.com',5], - [6,'jane.doe@example.com',1], + [6,'jane.doe@example.com',5], ], ], ]; @@ -86,4 +81,23 @@ trait SeriesIcon { protected function tearDownSeriesIcon(): void { unset($this->data); } + + public function testListTheIconsOfAUser() { + $exp = [ + ['id' => 1,'url' => 'http://localhost:8000/Icon/PNG', 'type' => 'image/png', 'data' => base64_decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAAADUlEQVQYV2NgYGBgAAAABQABijPjAAAAAABJRU5ErkJggg==")], + ['id' => 2,'url' => 'http://localhost:8000/Icon/GIF', 'type' => 'image/gif', 'data' => base64_decode("R0lGODlhAQABAIABAAAAAP///yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==")], + ['id' => 3,'url' => 'http://localhost:8000/Icon/SVG1', 'type' => 'image/svg+xml', 'data' => ''], + ]; + $this->assertResult($exp, Arsse::$db->iconList("john.doe@example.com")); + $exp = [ + ['id' => 2,'url' => 'http://localhost:8000/Icon/GIF', 'type' => 'image/gif', 'data' => base64_decode("R0lGODlhAQABAIABAAAAAP///yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==")], + ]; + $this->assertResult($exp, Arsse::$db->iconList("jane.doe@example.com")); + } + + public function testListTheIconsOfAUserWithoutAuthority() { + \Phake::when(Arsse::$user)->authorize->thenReturn(false); + $this->assertException("notAuthorized", "User", "ExceptionAuthz"); + Arsse::$db->iconList("jane.doe@example.com"); + } } diff --git a/tests/cases/Service/TestService.php b/tests/cases/Service/TestService.php index 804cd553..9aef50cb 100644 --- a/tests/cases/Service/TestService.php +++ b/tests/cases/Service/TestService.php @@ -43,6 +43,7 @@ class TestService extends \JKingWeb\Arsse\Test\AbstractTest { public function testPerformPreCleanup(): void { $this->assertTrue(Service::cleanupPre()); \Phake::verify(Arsse::$db)->feedCleanup(); + \Phake::verify(Arsse::$db)->iconCleanup(); \Phake::verify(Arsse::$db)->sessionCleanup(); } @@ -76,6 +77,7 @@ class TestService extends \JKingWeb\Arsse\Test\AbstractTest { \Phake::verify($d)->exec(); \Phake::verify($d)->clean(); \Phake::verify(Arsse::$db)->feedCleanup(); + \Phake::verify(Arsse::$db)->iconCleanup(); \Phake::verify(Arsse::$db)->sessionCleanup(); \Phake::verify(Arsse::$db)->articleCleanup(); \Phake::verify(Arsse::$db)->metaSet("service_last_checkin", $this->anything(), "datetime");