diff --git a/tests/cases/Database/Base.php b/tests/cases/Database/Base.php index de6d39e..537002f 100644 --- a/tests/cases/Database/Base.php +++ b/tests/cases/Database/Base.php @@ -8,11 +8,7 @@ namespace JKingWeb\Arsse\TestCase\Database; use JKingWeb\Arsse\Test\Database; use JKingWeb\Arsse\Arsse; -use JKingWeb\Arsse\Conf; use JKingWeb\Arsse\User; -use JKingWeb\Arsse\Misc\ValueInfo; -use JKingWeb\Arsse\Db\Result; -use JKingWeb\Arsse\Test\DatabaseInformation; use Phake; abstract class Base extends \JKingWeb\Arsse\Test\AbstractTest { @@ -84,7 +80,7 @@ abstract class Base extends \JKingWeb\Arsse\Test\AbstractTest { $this->$setUp(); // prime the database with series data if it hasn't already been done if (!$this->primed && isset($this->data)) { - $this->primeDatabase($this->data); + $this->primeDatabase(static::$drv, $this->data); } } @@ -111,121 +107,4 @@ abstract class Base extends \JKingWeb\Arsse\Test\AbstractTest { static::$failureReason = ""; static::clearData(); } - - public function primeDatabase(array $data): bool { - $drv = static::$drv; - $tr = $drv->begin(); - foreach ($data as $table => $info) { - $cols = array_map(function($v) { - return '"'.str_replace('"', '""', $v).'"'; - }, array_keys($info['columns'])); - $cols = implode(",", $cols); - $bindings = array_values($info['columns']); - $params = implode(",", array_fill(0, sizeof($info['columns']), "?")); - $s = $drv->prepareArray("INSERT INTO $table($cols) values($params)", $bindings); - foreach ($info['rows'] as $row) { - $s->runArray($row); - } - } - $tr->commit(); - $this->primed = true; - return true; - } - - public function compareExpectations(array $expected): bool { - foreach ($expected as $table => $info) { - $cols = array_map(function($v) { - return '"'.str_replace('"', '""', $v).'"'; - }, array_keys($info['columns'])); - $cols = implode(",", $cols); - $types = $info['columns']; - $data = static::$drv->prepare("SELECT $cols from $table")->run()->getAll(); - $cols = array_keys($info['columns']); - foreach ($info['rows'] as $index => $row) { - $this->assertCount(sizeof($cols), $row, "The number of values for array index $index does not match the number of fields"); - $row = array_combine($cols, $row); - foreach ($data as $index => $test) { - foreach ($test as $col => $value) { - switch ($types[$col]) { - case "datetime": - $test[$col] = $this->approximateTime($row[$col], $value); - break; - case "int": - $test[$col] = ValueInfo::normalize($value, ValueInfo::T_INT | ValueInfo::M_DROP | valueInfo::M_NULL); - break; - case "float": - $test[$col] = ValueInfo::normalize($value, ValueInfo::T_FLOAT | ValueInfo::M_DROP | valueInfo::M_NULL); - break; - case "bool": - $test[$col] = (int) ValueInfo::normalize($value, ValueInfo::T_BOOL | ValueInfo::M_DROP | valueInfo::M_NULL); - break; - } - } - if ($row===$test) { - $data[$index] = $test; - break; - } - } - $this->assertContains($row, $data, "Table $table does not contain record at array index $index."); - $found = array_search($row, $data, true); - unset($data[$found]); - } - $this->assertSame([], $data); - } - return true; - } - - public function primeExpectations(array $source, array $tableSpecs = null): array { - $out = []; - foreach ($tableSpecs as $table => $columns) { - // make sure the source has the table we want - $this->assertArrayHasKey($table, $source, "Source for expectations does not contain requested table $table."); - $out[$table] = [ - 'columns' => [], - 'rows' => array_fill(0, sizeof($source[$table]['rows']), []), - ]; - // make sure the source has all the columns we want for the table - $cols = array_flip($columns); - $cols = array_intersect_key($cols, $source[$table]['columns']); - $this->assertSame(array_keys($cols), $columns, "Source for table $table does not contain all requested columns"); - // get a map of source value offsets and keys - $targets = array_flip(array_keys($source[$table]['columns'])); - foreach ($cols as $key => $order) { - // fill the column-spec - $out[$table]['columns'][$key] = $source[$table]['columns'][$key]; - foreach ($source[$table]['rows'] as $index => $row) { - // fill each row column-wise with re-ordered values - $out[$table]['rows'][$index][$order] = $row[$targets[$key]]; - } - } - } - return $out; - } - - public function assertResult(array $expected, Result $data) { - $data = $data->getAll(); - $this->assertCount(sizeof($expected), $data, "Number of result rows (".sizeof($data).") differs from number of expected rows (".sizeof($expected).")"); - if (sizeof($expected)) { - // make sure the expectations are consistent - foreach ($expected as $exp) { - if (!isset($keys)) { - $keys = $exp; - continue; - } - $this->assertSame(array_keys($keys), array_keys($exp), "Result set expectations are irregular"); - } - // filter the result set to contain just the desired keys (we don't care if the result has extra keys) - $rows = []; - foreach ($data as $row) { - $rows[] = array_intersect_key($row, $keys); - } - // compare the result set to the expectations - foreach ($rows as $row) { - $this->assertContains($row, $expected, "Result set contains unexpected record."); - $found = array_search($row, $expected); - unset($expected[$found]); - } - $this->assertArraySubset($expected, [], false, "Expectations not in result set."); - } - } } diff --git a/tests/cases/Database/SeriesArticle.php b/tests/cases/Database/SeriesArticle.php index 048cd18..2deaeb7 100644 --- a/tests/cases/Database/SeriesArticle.php +++ b/tests/cases/Database/SeriesArticle.php @@ -581,7 +581,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][9][4] = $now; $state['arsse_marks']['rows'][11][2] = 0; $state['arsse_marks']['rows'][11][4] = $now; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkAllArticlesRead() { @@ -596,7 +596,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][] = [13,6,1,0,$now,'']; $state['arsse_marks']['rows'][] = [14,7,1,0,$now,'']; $state['arsse_marks']['rows'][] = [14,8,1,0,$now,'']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkAllArticlesUnstarred() { @@ -607,7 +607,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][10][4] = $now; $state['arsse_marks']['rows'][11][3] = 0; $state['arsse_marks']['rows'][11][4] = $now; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkAllArticlesStarred() { @@ -622,7 +622,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][] = [13,6,0,1,$now,'']; $state['arsse_marks']['rows'][] = [14,7,0,1,$now,'']; $state['arsse_marks']['rows'][] = [14,8,0,1,$now,'']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkAllArticlesUnreadAndUnstarred() { @@ -636,7 +636,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][11][2] = 0; $state['arsse_marks']['rows'][11][3] = 0; $state['arsse_marks']['rows'][11][4] = $now; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkAllArticlesReadAndStarred() { @@ -654,7 +654,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][] = [13,6,1,1,$now,'']; $state['arsse_marks']['rows'][] = [14,7,1,1,$now,'']; $state['arsse_marks']['rows'][] = [14,8,1,1,$now,'']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkAllArticlesUnreadAndStarred() { @@ -672,7 +672,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][] = [13,6,0,1,$now,'']; $state['arsse_marks']['rows'][] = [14,7,0,1,$now,'']; $state['arsse_marks']['rows'][] = [14,8,0,1,$now,'']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkAllArticlesReadAndUnstarred() { @@ -690,7 +690,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][] = [13,6,1,0,$now,'']; $state['arsse_marks']['rows'][] = [14,7,1,0,$now,'']; $state['arsse_marks']['rows'][] = [14,8,1,0,$now,'']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testSetNoteForAllArticles() { @@ -709,7 +709,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][] = [13,6,0,0,$now,'New note']; $state['arsse_marks']['rows'][] = [14,7,0,0,$now,'New note']; $state['arsse_marks']['rows'][] = [14,8,0,0,$now,'New note']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkATreeFolder() { @@ -720,7 +720,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][] = [13,6,1,0,$now,'']; $state['arsse_marks']['rows'][] = [14,7,1,0,$now,'']; $state['arsse_marks']['rows'][] = [14,8,1,0,$now,'']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkALeafFolder() { @@ -729,7 +729,7 @@ trait SeriesArticle { $state = $this->primeExpectations($this->data, $this->checkTables); $state['arsse_marks']['rows'][] = [13,5,1,0,$now,'']; $state['arsse_marks']['rows'][] = [13,6,1,0,$now,'']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkAMissingFolder() { @@ -743,7 +743,7 @@ trait SeriesArticle { $state = $this->primeExpectations($this->data, $this->checkTables); $state['arsse_marks']['rows'][] = [13,5,1,0,$now,'']; $state['arsse_marks']['rows'][] = [13,6,1,0,$now,'']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkAMissingSubscription() { @@ -757,7 +757,7 @@ trait SeriesArticle { $state = $this->primeExpectations($this->data, $this->checkTables); $state['arsse_marks']['rows'][9][3] = 1; $state['arsse_marks']['rows'][9][4] = $now; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkMultipleArticles() { @@ -767,7 +767,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][9][3] = 1; $state['arsse_marks']['rows'][9][4] = $now; $state['arsse_marks']['rows'][] = [14,7,0,1,$now,'']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkMultipleArticlessUnreadAndStarred() { @@ -780,7 +780,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][11][2] = 0; $state['arsse_marks']['rows'][11][4] = $now; $state['arsse_marks']['rows'][] = [14,7,0,1,$now,'']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkTooFewMultipleArticles() { @@ -803,7 +803,7 @@ trait SeriesArticle { $state = $this->primeExpectations($this->data, $this->checkTables); $state['arsse_marks']['rows'][9][3] = 1; $state['arsse_marks']['rows'][9][4] = $now; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkMultipleEditions() { @@ -813,13 +813,13 @@ trait SeriesArticle { $state['arsse_marks']['rows'][9][3] = 1; $state['arsse_marks']['rows'][9][4] = $now; $state['arsse_marks']['rows'][] = [14,7,0,1,$now,'']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkMultipleMissingEditions() { $this->assertSame(0, Arsse::$db->articleMark($this->user, ['starred'=>true], (new Context)->editions([500,501]))); $state = $this->primeExpectations($this->data, $this->checkTables); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkMultipleEditionsUnread() { @@ -830,7 +830,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][9][4] = $now; $state['arsse_marks']['rows'][11][2] = 0; $state['arsse_marks']['rows'][11][4] = $now; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkMultipleEditionsUnreadWithStale() { @@ -839,7 +839,7 @@ trait SeriesArticle { $state = $this->primeExpectations($this->data, $this->checkTables); $state['arsse_marks']['rows'][11][2] = 0; $state['arsse_marks']['rows'][11][4] = $now; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkMultipleEditionsUnreadAndStarredWithStale() { @@ -851,7 +851,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][11][2] = 0; $state['arsse_marks']['rows'][11][4] = $now; $state['arsse_marks']['rows'][] = [14,7,0,1,$now,'']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkTooFewMultipleEditions() { @@ -866,7 +866,7 @@ trait SeriesArticle { public function testMarkAStaleEditionUnread() { Arsse::$db->articleMark($this->user, ['read'=>false], (new Context)->edition(20)); // no changes occur $state = $this->primeExpectations($this->data, $this->checkTables); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkAStaleEditionStarred() { @@ -875,7 +875,7 @@ trait SeriesArticle { $state = $this->primeExpectations($this->data, $this->checkTables); $state['arsse_marks']['rows'][9][3] = 1; $state['arsse_marks']['rows'][9][4] = $now; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkAStaleEditionUnreadAndStarred() { @@ -884,13 +884,13 @@ trait SeriesArticle { $state = $this->primeExpectations($this->data, $this->checkTables); $state['arsse_marks']['rows'][9][3] = 1; $state['arsse_marks']['rows'][9][4] = $now; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkAStaleEditionUnreadAndUnstarred() { Arsse::$db->articleMark($this->user, ['read'=>false,'starred'=>false], (new Context)->edition(20)); // no changes occur $state = $this->primeExpectations($this->data, $this->checkTables); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkAMissingEdition() { @@ -906,7 +906,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][8][4] = $now; $state['arsse_marks']['rows'][9][3] = 1; $state['arsse_marks']['rows'][9][4] = $now; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkByLatestEdition() { @@ -919,7 +919,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][] = [13,6,0,1,$now,'']; $state['arsse_marks']['rows'][] = [14,7,0,1,$now,'']; $state['arsse_marks']['rows'][] = [14,8,0,1,$now,'']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkByLastMarked() { @@ -930,7 +930,7 @@ trait SeriesArticle { $state['arsse_marks']['rows'][8][4] = $now; $state['arsse_marks']['rows'][9][3] = 1; $state['arsse_marks']['rows'][9][4] = $now; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkByNotLastMarked() { @@ -939,7 +939,7 @@ trait SeriesArticle { $state = $this->primeExpectations($this->data, $this->checkTables); $state['arsse_marks']['rows'][] = [13,5,0,1,$now,'']; $state['arsse_marks']['rows'][] = [14,7,0,1,$now,'']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMarkArticlesWithoutAuthority() { diff --git a/tests/cases/Database/SeriesCleanup.php b/tests/cases/Database/SeriesCleanup.php index 6d80a7e..9d2e009 100644 --- a/tests/cases/Database/SeriesCleanup.php +++ b/tests/cases/Database/SeriesCleanup.php @@ -161,7 +161,7 @@ trait SeriesCleanup { $state['arsse_feeds']['rows'][0][1] = null; unset($state['arsse_feeds']['rows'][1]); $state['arsse_feeds']['rows'][2][1] = $now; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testCleanUpOrphanedFeedsWithUnlimitedRetention() { @@ -175,7 +175,7 @@ trait SeriesCleanup { ]); $state['arsse_feeds']['rows'][0][1] = null; $state['arsse_feeds']['rows'][2][1] = $now; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testCleanUpOldArticlesWithStandardRetention() { @@ -186,7 +186,7 @@ trait SeriesCleanup { foreach ([7,8,9] as $id) { unset($state['arsse_articles']['rows'][$id - 1]); } - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testCleanUpOldArticlesWithUnlimitedReadRetention() { @@ -200,7 +200,7 @@ trait SeriesCleanup { foreach ([7,8] as $id) { unset($state['arsse_articles']['rows'][$id - 1]); } - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testCleanUpOldArticlesWithUnlimitedUnreadRetention() { @@ -214,7 +214,7 @@ trait SeriesCleanup { foreach ([9] as $id) { unset($state['arsse_articles']['rows'][$id - 1]); } - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testCleanUpOldArticlesWithUnlimitedRetention() { @@ -226,7 +226,7 @@ trait SeriesCleanup { $state = $this->primeExpectations($this->data, [ 'arsse_articles' => ["id"] ]); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testCleanUpExpiredSessions() { @@ -237,7 +237,7 @@ trait SeriesCleanup { foreach ([3,4,5] as $id) { unset($state['arsse_sessions']['rows'][$id - 1]); } - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testCleanUpExpiredTokens() { @@ -248,6 +248,6 @@ trait SeriesCleanup { foreach ([2] as $id) { unset($state['arsse_tokens']['rows'][$id - 1]); } - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } } diff --git a/tests/cases/Database/SeriesFeed.php b/tests/cases/Database/SeriesFeed.php index a01f064..8576bdf 100644 --- a/tests/cases/Database/SeriesFeed.php +++ b/tests/cases/Database/SeriesFeed.php @@ -204,7 +204,7 @@ trait SeriesFeed { $state['arsse_marks']['rows'][3] = [6,4,0,0,$now]; $state['arsse_marks']['rows'][6] = [1,3,0,0,$now]; $state['arsse_feeds']['rows'][0] = [1,6]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); // update a valid feed which previously had an error Arsse::$db->feedUpdate(2); // update an erroneous feed which previously had no errors @@ -214,12 +214,12 @@ trait SeriesFeed { ]); $state['arsse_feeds']['rows'][1] = [2,0,""]; $state['arsse_feeds']['rows'][2] = [3,1,'Feed URL "http://localhost:8000/Feed/Fetching/Error?code=404" is invalid']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); // update the bad feed again, twice Arsse::$db->feedUpdate(3); Arsse::$db->feedUpdate(3); $state['arsse_feeds']['rows'][2] = [3,3,'Feed URL "http://localhost:8000/Feed/Fetching/Error?code=404" is invalid']; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testUpdateAMissingFeed() { @@ -254,7 +254,7 @@ trait SeriesFeed { ["Bodybuilders"], ["Men"], ]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testListStaleFeeds() { diff --git a/tests/cases/Database/SeriesFolder.php b/tests/cases/Database/SeriesFolder.php index 9643b64..367c024 100644 --- a/tests/cases/Database/SeriesFolder.php +++ b/tests/cases/Database/SeriesFolder.php @@ -105,7 +105,7 @@ trait SeriesFolder { Phake::verify(Arsse::$user)->authorize($user, "folderAdd"); $state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'name']]); $state['arsse_folders']['rows'][] = [$folderID, $user, null, "Entertainment"]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testAddADuplicateRootFolder() { @@ -120,7 +120,7 @@ trait SeriesFolder { Phake::verify(Arsse::$user)->authorize($user, "folderAdd"); $state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'name']]); $state['arsse_folders']['rows'][] = [$folderID, $user, 2, "GNOME"]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testAddANestedFolderToAMissingParent() { @@ -218,7 +218,7 @@ trait SeriesFolder { Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "folderRemove"); $state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'name']]); array_pop($state['arsse_folders']['rows']); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testRemoveAFolderTree() { @@ -228,7 +228,7 @@ trait SeriesFolder { foreach ([0,1,2,5] as $index) { unset($state['arsse_folders']['rows'][$index]); } - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testRemoveAMissingFolder() { @@ -292,7 +292,7 @@ trait SeriesFolder { Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "folderPropertiesSet"); $state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'name']]); $state['arsse_folders']['rows'][5][3] = "Opinion"; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testRenameTheRootFolder() { @@ -319,7 +319,7 @@ trait SeriesFolder { Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "folderPropertiesSet"); $state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'name']]); $state['arsse_folders']['rows'][5][2] = 5; // parent should have changed - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMoveTheRootFolder() { diff --git a/tests/cases/Database/SeriesLabel.php b/tests/cases/Database/SeriesLabel.php index 1f11004..ec767e6 100644 --- a/tests/cases/Database/SeriesLabel.php +++ b/tests/cases/Database/SeriesLabel.php @@ -257,7 +257,7 @@ trait SeriesLabel { Phake::verify(Arsse::$user)->authorize($user, "labelAdd"); $state = $this->primeExpectations($this->data, $this->checkLabels); $state['arsse_labels']['rows'][] = [$labelID, $user, "Entertaining"]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testAddADuplicateLabel() { @@ -313,7 +313,7 @@ trait SeriesLabel { Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "labelRemove"); $state = $this->primeExpectations($this->data, $this->checkLabels); array_shift($state['arsse_labels']['rows']); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testRemoveALabelByName() { @@ -321,7 +321,7 @@ trait SeriesLabel { Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "labelRemove"); $state = $this->primeExpectations($this->data, $this->checkLabels); array_shift($state['arsse_labels']['rows']); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testRemoveAMissingLabel() { @@ -397,7 +397,7 @@ trait SeriesLabel { Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "labelPropertiesSet"); $state = $this->primeExpectations($this->data, $this->checkLabels); $state['arsse_labels']['rows'][0][2] = "Curious"; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testRenameALabelByName() { @@ -405,7 +405,7 @@ trait SeriesLabel { Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "labelPropertiesSet"); $state = $this->primeExpectations($this->data, $this->checkLabels); $state['arsse_labels']['rows'][0][2] = "Curious"; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testRenameALabelToTheEmptyString() { @@ -487,14 +487,14 @@ trait SeriesLabel { $state = $this->primeExpectations($this->data, $this->checkMembers); $state['arsse_label_members']['rows'][4][3] = 1; $state['arsse_label_members']['rows'][] = [1,2,1,1]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testClearALabelFromArticles() { Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([1,5]), Database::ASSOC_REMOVE); $state = $this->primeExpectations($this->data, $this->checkMembers); $state['arsse_label_members']['rows'][0][3] = 0; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testApplyALabelToArticlesByName() { @@ -502,26 +502,26 @@ trait SeriesLabel { $state = $this->primeExpectations($this->data, $this->checkMembers); $state['arsse_label_members']['rows'][4][3] = 1; $state['arsse_label_members']['rows'][] = [1,2,1,1]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testClearALabelFromArticlesByName() { Arsse::$db->labelArticlesSet("john.doe@example.com", "Interesting", (new Context)->articles([1,5]), Database::ASSOC_REMOVE, true); $state = $this->primeExpectations($this->data, $this->checkMembers); $state['arsse_label_members']['rows'][0][3] = 0; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testApplyALabelToNoArticles() { Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([10000])); $state = $this->primeExpectations($this->data, $this->checkMembers); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testClearALabelFromNoArticles() { Arsse::$db->labelArticlesSet("john.doe@example.com", 1, (new Context)->articles([10000]), Database::ASSOC_REMOVE); $state = $this->primeExpectations($this->data, $this->checkMembers); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testReplaceArticlesOfALabel() { @@ -531,7 +531,7 @@ trait SeriesLabel { $state['arsse_label_members']['rows'][2][3] = 0; $state['arsse_label_members']['rows'][4][3] = 1; $state['arsse_label_members']['rows'][] = [1,2,1,1]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testPurgeArticlesOfALabel() { @@ -539,7 +539,7 @@ trait SeriesLabel { $state = $this->primeExpectations($this->data, $this->checkMembers); $state['arsse_label_members']['rows'][0][3] = 0; $state['arsse_label_members']['rows'][2][3] = 0; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testApplyALabelToArticlesWithoutAuthority() { diff --git a/tests/cases/Database/SeriesMeta.php b/tests/cases/Database/SeriesMeta.php index 538700a..485c715 100644 --- a/tests/cases/Database/SeriesMeta.php +++ b/tests/cases/Database/SeriesMeta.php @@ -28,7 +28,7 @@ trait SeriesMeta { // 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($dataBare); + $this->primeDatabase(static::$drv, $dataBare); } protected function tearDownSeriesMeta() { @@ -39,7 +39,7 @@ trait SeriesMeta { $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); + $this->compareExpectations(static::$drv, $state); } public function testAddANewTypedValue() { @@ -52,14 +52,14 @@ trait SeriesMeta { $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); + $this->compareExpectations(static::$drv, $state); } public 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); + $this->compareExpectations(static::$drv, $state); } public function testRemoveAValue() { @@ -67,7 +67,7 @@ trait SeriesMeta { $this->assertFalse(Arsse::$db->metaRemove("album")); $state = $this->primeExpectations($this->data, ['arsse_meta' => ['key','value']]); unset($state['arsse_meta']['rows'][1]); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testRetrieveAValue() { diff --git a/tests/cases/Database/SeriesSession.php b/tests/cases/Database/SeriesSession.php index 74a809c..ad9a45b 100644 --- a/tests/cases/Database/SeriesSession.php +++ b/tests/cases/Database/SeriesSession.php @@ -69,7 +69,7 @@ trait SeriesSession { // sessions near timeout should be refreshed automatically $state = $this->primeExpectations($this->data, ['arsse_sessions' => ["id", "created", "expires", "user"]]); $state['arsse_sessions']['rows'][3][2] = Date::transform(Date::add(Arsse::$conf->userSessionTimeout, $now), "sql"); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); // session resumption should not check authorization Phake::when(Arsse::$user)->authorize->thenReturn(false); $this->assertArraySubset($exp1, Arsse::$db->sessionResume("80fa94c1a11f11e78667001e673b2560")); @@ -96,7 +96,7 @@ trait SeriesSession { $now = time(); $state = $this->primeExpectations($this->data, ['arsse_sessions' => ["id", "created", "expires", "user"]]); $state['arsse_sessions']['rows'][] = [$id, Date::transform($now, "sql"), Date::transform(Date::add(Arsse::$conf->userSessionTimeout, $now), "sql"), $user]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testCreateASessionWithoutAuthority() { @@ -111,7 +111,7 @@ trait SeriesSession { $this->assertTrue(Arsse::$db->sessionDestroy($user, $id)); $state = $this->primeExpectations($this->data, ['arsse_sessions' => ["id", "created", "expires", "user"]]); unset($state['arsse_sessions']['rows'][0]); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); // destroying a session which does not exist is not an error $this->assertFalse(Arsse::$db->sessionDestroy($user, $id)); } diff --git a/tests/cases/Database/SeriesSubscription.php b/tests/cases/Database/SeriesSubscription.php index d65fb3e..be06be8 100644 --- a/tests/cases/Database/SeriesSubscription.php +++ b/tests/cases/Database/SeriesSubscription.php @@ -160,7 +160,7 @@ trait SeriesSubscription { 'arsse_subscriptions' => ['id','owner','feed'], ]); $state['arsse_subscriptions']['rows'][] = [$subID,$this->user,1]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testAddASubscriptionToANewFeed() { @@ -177,7 +177,7 @@ trait SeriesSubscription { ]); $state['arsse_feeds']['rows'][] = [$feedID,$url,"",""]; $state['arsse_subscriptions']['rows'][] = [$subID,$this->user,$feedID]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testAddASubscriptionToANewFeedViaDiscovery() { @@ -195,7 +195,7 @@ trait SeriesSubscription { ]); $state['arsse_feeds']['rows'][] = [$feedID,$discovered,"",""]; $state['arsse_subscriptions']['rows'][] = [$subID,$this->user,$feedID]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testAddASubscriptionToAnInvalidFeed() { @@ -211,7 +211,7 @@ trait SeriesSubscription { 'arsse_feeds' => ['id','url','username','password'], 'arsse_subscriptions' => ['id','owner','feed'], ]); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); $this->assertException("invalidUrl", "Feed"); throw $e; } @@ -238,7 +238,7 @@ trait SeriesSubscription { 'arsse_subscriptions' => ['id','owner','feed'], ]); array_shift($state['arsse_subscriptions']['rows']); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testRemoveAMissingSubscription() { @@ -377,15 +377,15 @@ trait SeriesSubscription { 'arsse_subscriptions' => ['id','owner','feed','title','folder','pinned','order_type'], ]); $state['arsse_subscriptions']['rows'][0] = [1,"john.doe@example.com",2,"Ook Ook",3,0,0]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); Arsse::$db->subscriptionPropertiesSet($this->user, 1, [ 'title' => null, ]); $state['arsse_subscriptions']['rows'][0] = [1,"john.doe@example.com",2,null,3,0,0]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); // making no changes is a valid result Arsse::$db->subscriptionPropertiesSet($this->user, 1, ['unhinged' => true]); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testMoveASubscriptionToAMissingFolder() { diff --git a/tests/cases/Database/SeriesTag.php b/tests/cases/Database/SeriesTag.php index b3ff4e4..ddd52cd 100644 --- a/tests/cases/Database/SeriesTag.php +++ b/tests/cases/Database/SeriesTag.php @@ -117,7 +117,7 @@ trait SeriesTag { Phake::verify(Arsse::$user)->authorize($user, "tagAdd"); $state = $this->primeExpectations($this->data, $this->checkTags); $state['arsse_tags']['rows'][] = [$tagID, $user, "Entertaining"]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testAddADuplicateTag() { @@ -173,7 +173,7 @@ trait SeriesTag { Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "tagRemove"); $state = $this->primeExpectations($this->data, $this->checkTags); array_shift($state['arsse_tags']['rows']); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testRemoveATagByName() { @@ -181,7 +181,7 @@ trait SeriesTag { Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "tagRemove"); $state = $this->primeExpectations($this->data, $this->checkTags); array_shift($state['arsse_tags']['rows']); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testRemoveAMissingTag() { @@ -255,7 +255,7 @@ trait SeriesTag { Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "tagPropertiesSet"); $state = $this->primeExpectations($this->data, $this->checkTags); $state['arsse_tags']['rows'][0][2] = "Curious"; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testRenameATagByName() { @@ -263,7 +263,7 @@ trait SeriesTag { Phake::verify(Arsse::$user)->authorize("john.doe@example.com", "tagPropertiesSet"); $state = $this->primeExpectations($this->data, $this->checkTags); $state['arsse_tags']['rows'][0][2] = "Curious"; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testRenameATagToTheEmptyString() { @@ -345,14 +345,14 @@ trait SeriesTag { $state = $this->primeExpectations($this->data, $this->checkMembers); $state['arsse_tag_members']['rows'][1][2] = 1; $state['arsse_tag_members']['rows'][] = [1,4,1]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testClearATagFromSubscriptions() { Arsse::$db->tagSubscriptionsSet("john.doe@example.com", 1, [1,3], Database::ASSOC_REMOVE); $state = $this->primeExpectations($this->data, $this->checkMembers); $state['arsse_tag_members']['rows'][0][2] = 0; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testApplyATagToSubscriptionsByName() { @@ -360,26 +360,26 @@ trait SeriesTag { $state = $this->primeExpectations($this->data, $this->checkMembers); $state['arsse_tag_members']['rows'][1][2] = 1; $state['arsse_tag_members']['rows'][] = [1,4,1]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testClearATagFromSubscriptionsByName() { Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [1,3], Database::ASSOC_REMOVE, true); $state = $this->primeExpectations($this->data, $this->checkMembers); $state['arsse_tag_members']['rows'][0][2] = 0; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testApplyATagToNoSubscriptionsByName() { Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [], Database::ASSOC_ADD, true); $state = $this->primeExpectations($this->data, $this->checkMembers); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testClearATagFromNoSubscriptionsByName() { Arsse::$db->tagSubscriptionsSet("john.doe@example.com", "Interesting", [], Database::ASSOC_REMOVE, true); $state = $this->primeExpectations($this->data, $this->checkMembers); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testReplaceSubscriptionsOfATag() { @@ -389,7 +389,7 @@ trait SeriesTag { $state['arsse_tag_members']['rows'][1][2] = 1; $state['arsse_tag_members']['rows'][2][2] = 0; $state['arsse_tag_members']['rows'][] = [1,4,1]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testPurgeSubscriptionsOfATag() { @@ -397,7 +397,7 @@ trait SeriesTag { $state = $this->primeExpectations($this->data, $this->checkMembers); $state['arsse_tag_members']['rows'][0][2] = 0; $state['arsse_tag_members']['rows'][2][2] = 0; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testApplyATagToSubscriptionsWithoutAuthority() { diff --git a/tests/cases/Database/SeriesToken.php b/tests/cases/Database/SeriesToken.php index ff85407..ef223df 100644 --- a/tests/cases/Database/SeriesToken.php +++ b/tests/cases/Database/SeriesToken.php @@ -87,13 +87,13 @@ trait SeriesToken { $state = $this->primeExpectations($this->data, ['arsse_tokens' => ["id", "class", "expires", "user"]]); $id = Arsse::$db->tokenCreate($user, "fever.login"); $state['arsse_tokens']['rows'][] = [$id, "fever.login", null, $user]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); $id = Arsse::$db->tokenCreate($user, "fever.login", null, new \DateTime("2020-01-01T00:00:00Z")); $state['arsse_tokens']['rows'][] = [$id, "fever.login", "2020-01-01 00:00:00", $user]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); Arsse::$db->tokenCreate($user, "fever.login", "token!", new \DateTime("2021-01-01T00:00:00Z")); $state['arsse_tokens']['rows'][] = ["token!", "fever.login", "2021-01-01 00:00:00", $user]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testCreateATokenForAMissingUser() { @@ -113,7 +113,7 @@ trait SeriesToken { $this->assertTrue(Arsse::$db->tokenRevoke($user, "fever.login", $id)); $state = $this->primeExpectations($this->data, ['arsse_tokens' => ["id", "expires", "user"]]); unset($state['arsse_tokens']['rows'][0]); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); // revoking a token which does not exist is not an error $this->assertFalse(Arsse::$db->tokenRevoke($user, "fever.login", $id)); } @@ -124,10 +124,10 @@ trait SeriesToken { $this->assertTrue(Arsse::$db->tokenRevoke($user, "fever.login")); unset($state['arsse_tokens']['rows'][0]); unset($state['arsse_tokens']['rows'][1]); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); $this->assertTrue(Arsse::$db->tokenRevoke($user, "class.class")); unset($state['arsse_tokens']['rows'][2]); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); // revoking tokens which do not exist is not an error $this->assertFalse(Arsse::$db->tokenRevoke($user, "unknown.class")); } diff --git a/tests/cases/Database/SeriesUser.php b/tests/cases/Database/SeriesUser.php index 8036bee..8395edc 100644 --- a/tests/cases/Database/SeriesUser.php +++ b/tests/cases/Database/SeriesUser.php @@ -36,7 +36,7 @@ trait SeriesUser { $this->assertFalse(Arsse::$db->userExists("jane.doe@example.org")); Phake::verify(Arsse::$user)->authorize("jane.doe@example.com", "userExists"); Phake::verify(Arsse::$user)->authorize("jane.doe@example.org", "userExists"); - $this->compareExpectations($this->data); + $this->compareExpectations(static::$drv, $this->data); } public function testCheckThatAUserExistsWithoutAuthority() { @@ -68,7 +68,7 @@ trait SeriesUser { Phake::verify(Arsse::$user)->authorize("john.doe@example.org", "userAdd"); $state = $this->primeExpectations($this->data, ['arsse_users' => ['id']]); $state['arsse_users']['rows'][] = ["john.doe@example.org"]; - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testAddAnExistingUser() { @@ -87,7 +87,7 @@ trait SeriesUser { Phake::verify(Arsse::$user)->authorize("admin@example.net", "userRemove"); $state = $this->primeExpectations($this->data, ['arsse_users' => ['id']]); array_shift($state['arsse_users']['rows']); - $this->compareExpectations($state); + $this->compareExpectations(static::$drv, $state); } public function testRemoveAMissingUser() { diff --git a/tests/lib/AbstractTest.php b/tests/lib/AbstractTest.php index f55ca9b..6b4de82 100644 --- a/tests/lib/AbstractTest.php +++ b/tests/lib/AbstractTest.php @@ -9,14 +9,15 @@ namespace JKingWeb\Arsse\Test; use JKingWeb\Arsse\Exception; use JKingWeb\Arsse\Arsse; use JKingWeb\Arsse\Conf; -use JKingWeb\Arsse\CLI; +use JKingWeb\Arsse\Db\Driver; +use JKingWeb\Arsse\Db\Result; use JKingWeb\Arsse\Misc\Date; +use JKingWeb\Arsse\Misc\ValueInfo; use Psr\Http\Message\MessageInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Zend\Diactoros\Response\JsonResponse; -use Zend\Diactoros\Response\EmptyResponse; /** @coversNothing */ abstract class AbstractTest extends \PHPUnit\Framework\TestCase { @@ -135,4 +136,120 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase { } return $value; } + + public function primeDatabase(Driver $drv, array $data): bool { + $tr = $drv->begin(); + foreach ($data as $table => $info) { + $cols = array_map(function($v) { + return '"'.str_replace('"', '""', $v).'"'; + }, array_keys($info['columns'])); + $cols = implode(",", $cols); + $bindings = array_values($info['columns']); + $params = implode(",", array_fill(0, sizeof($info['columns']), "?")); + $s = $drv->prepareArray("INSERT INTO $table($cols) values($params)", $bindings); + foreach ($info['rows'] as $row) { + $s->runArray($row); + } + } + $tr->commit(); + $this->primed = true; + return true; + } + + public function compareExpectations(Driver $drv, array $expected): bool { + foreach ($expected as $table => $info) { + $cols = array_map(function($v) { + return '"'.str_replace('"', '""', $v).'"'; + }, array_keys($info['columns'])); + $cols = implode(",", $cols); + $types = $info['columns']; + $data = $drv->prepare("SELECT $cols from $table")->run()->getAll(); + $cols = array_keys($info['columns']); + foreach ($info['rows'] as $index => $row) { + $this->assertCount(sizeof($cols), $row, "The number of values for array index $index does not match the number of fields"); + $row = array_combine($cols, $row); + foreach ($data as $index => $test) { + foreach ($test as $col => $value) { + switch ($types[$col]) { + case "datetime": + $test[$col] = $this->approximateTime($row[$col], $value); + break; + case "int": + $test[$col] = ValueInfo::normalize($value, ValueInfo::T_INT | ValueInfo::M_DROP | valueInfo::M_NULL); + break; + case "float": + $test[$col] = ValueInfo::normalize($value, ValueInfo::T_FLOAT | ValueInfo::M_DROP | valueInfo::M_NULL); + break; + case "bool": + $test[$col] = (int) ValueInfo::normalize($value, ValueInfo::T_BOOL | ValueInfo::M_DROP | valueInfo::M_NULL); + break; + } + } + if ($row===$test) { + $data[$index] = $test; + break; + } + } + $this->assertContains($row, $data, "Table $table does not contain record at array index $index."); + $found = array_search($row, $data, true); + unset($data[$found]); + } + $this->assertSame([], $data); + } + return true; + } + + public function primeExpectations(array $source, array $tableSpecs = null): array { + $out = []; + foreach ($tableSpecs as $table => $columns) { + // make sure the source has the table we want + $this->assertArrayHasKey($table, $source, "Source for expectations does not contain requested table $table."); + $out[$table] = [ + 'columns' => [], + 'rows' => array_fill(0, sizeof($source[$table]['rows']), []), + ]; + // make sure the source has all the columns we want for the table + $cols = array_flip($columns); + $cols = array_intersect_key($cols, $source[$table]['columns']); + $this->assertSame(array_keys($cols), $columns, "Source for table $table does not contain all requested columns"); + // get a map of source value offsets and keys + $targets = array_flip(array_keys($source[$table]['columns'])); + foreach ($cols as $key => $order) { + // fill the column-spec + $out[$table]['columns'][$key] = $source[$table]['columns'][$key]; + foreach ($source[$table]['rows'] as $index => $row) { + // fill each row column-wise with re-ordered values + $out[$table]['rows'][$index][$order] = $row[$targets[$key]]; + } + } + } + return $out; + } + + public function assertResult(array $expected, Result $data) { + $data = $data->getAll(); + $this->assertCount(sizeof($expected), $data, "Number of result rows (".sizeof($data).") differs from number of expected rows (".sizeof($expected).")"); + if (sizeof($expected)) { + // make sure the expectations are consistent + foreach ($expected as $exp) { + if (!isset($keys)) { + $keys = $exp; + continue; + } + $this->assertSame(array_keys($keys), array_keys($exp), "Result set expectations are irregular"); + } + // filter the result set to contain just the desired keys (we don't care if the result has extra keys) + $rows = []; + foreach ($data as $row) { + $rows[] = array_intersect_key($row, $keys); + } + // compare the result set to the expectations + foreach ($rows as $row) { + $this->assertContains($row, $expected, "Result set contains unexpected record."); + $found = array_search($row, $expected); + unset($expected[$found]); + } + $this->assertArraySubset($expected, [], false, "Expectations not in result set."); + } + } }