Browse Source

Make database test helpers generic

microsub
J. King 5 years ago
parent
commit
2628ff7bf4
  1. 123
      tests/cases/Database/Base.php
  2. 58
      tests/cases/Database/SeriesArticle.php
  3. 16
      tests/cases/Database/SeriesCleanup.php
  4. 8
      tests/cases/Database/SeriesFeed.php
  5. 12
      tests/cases/Database/SeriesFolder.php
  6. 26
      tests/cases/Database/SeriesLabel.php
  7. 10
      tests/cases/Database/SeriesMeta.php
  8. 6
      tests/cases/Database/SeriesSession.php
  9. 16
      tests/cases/Database/SeriesSubscription.php
  10. 26
      tests/cases/Database/SeriesTag.php
  11. 12
      tests/cases/Database/SeriesToken.php
  12. 6
      tests/cases/Database/SeriesUser.php
  13. 121
      tests/lib/AbstractTest.php

123
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.");
}
}
}

58
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() {

16
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);
}
}

8
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() {

12
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() {

26
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() {

10
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() {

6
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));
}

16
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() {

26
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() {

12
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"));
}

6
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() {

121
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.");
}
}
}

Loading…
Cancel
Save