Browse Source

Merge branch 'dbtest' into redup

redup
J. King 2 years ago
parent
commit
fbf7848c14
  1. 1
      .gitignore
  2. 30
      .php-cs-fixer.dist.php
  3. 56
      composer.lock
  4. 1
      lib/Context/ExclusionMembers.php
  5. 64
      lib/Database.php
  6. 4
      lib/Db/AbstractDriver.php
  7. 4
      lib/Db/Driver.php
  8. 8
      lib/Db/MySQL/Driver.php
  9. 2
      lib/Db/MySQL/PDODriver.php
  10. 4
      lib/Db/PostgreSQL/Driver.php
  11. 4
      lib/Db/PostgreSQL/PDODriver.php
  12. 1
      lib/Db/PostgreSQL/PDOResult.php
  13. 4
      lib/Db/SQLite3/PDODriver.php
  14. 1
      lib/Misc/URL.php
  15. 16
      lib/Misc/ValueInfo.php
  16. 4
      lib/REST.php
  17. 2
      lib/REST/TinyTinyRSS/Search.php
  18. 6
      lib/Service.php
  19. 124
      tests/cases/Database/SeriesArticle.php
  20. 64
      tests/cases/Database/SeriesCleanup.php
  21. 75
      tests/cases/Database/SeriesFeed.php
  22. 26
      tests/cases/Database/SeriesFolder.php
  23. 31
      tests/cases/Database/SeriesIcon.php
  24. 74
      tests/cases/Database/SeriesLabel.php
  25. 9
      tests/cases/Database/SeriesMeta.php
  26. 13
      tests/cases/Database/SeriesSession.php
  27. 82
      tests/cases/Database/SeriesSubscription.php
  28. 31
      tests/cases/Database/SeriesTag.php
  29. 14
      tests/cases/Database/SeriesToken.php
  30. 13
      tests/cases/Database/SeriesUser.php
  31. 2
      tests/cases/Exception/TestException.php
  32. 8
      tests/cases/Feed/TestException.php
  33. 39
      tests/cases/ImportExport/TestImportExport.php
  34. 8
      tests/cases/Misc/TestContext.php
  35. 6
      tests/cases/Misc/TestQuery.php
  36. 1
      tests/cases/Misc/TestURL.php
  37. 2
      tests/cases/REST/Miniflux/TestV1.php
  38. 2
      tests/cases/REST/NextcloudNews/TestV1_2.php
  39. 9
      tests/cases/REST/TestREST.php
  40. 12
      tests/cases/REST/TinyTinyRSS/TestAPI.php
  41. 2
      tests/cases/REST/TinyTinyRSS/TestIcon.php
  42. 2
      tests/cases/Service/TestService.php
  43. 323
      tests/lib/AbstractTest.php
  44. 4
      tests/lib/DatabaseDrivers/MySQL.php
  45. 4
      tests/lib/DatabaseDrivers/MySQLPDO.php
  46. 2
      vendor-bin/csfixer/composer.json
  47. 574
      vendor-bin/csfixer/composer.lock
  48. 113
      vendor-bin/daux/composer.lock
  49. 2
      vendor-bin/phpstan/composer.lock
  50. 80
      vendor-bin/phpunit/composer.lock
  51. 241
      vendor-bin/robo/composer.lock

1
.gitignore

@ -11,6 +11,7 @@
/arsse.db*
/config.php
/.php_cs.cache
/.php-cs-fixer.cache
/tests/.phpunit.result.cache
# Dependencies

30
.php_cs.dist → .php-cs-fixer.dist.php

@ -16,6 +16,8 @@ $paths = [
BASE."tests",
];
$rules = [
// PSR standard to apply
'@PSR12' => true,
// house rules where PSR series is silent
'align_multiline_comment' => ['comment_type' => "phpdocs_only"],
'array_syntax' => ['syntax' => "short"],
@ -48,30 +50,18 @@ $rules = [
'pow_to_exponentiation' => true,
'set_type_to_cast' => true,
'standardize_not_equals' => true,
'trailing_comma_in_multiline_array' => true,
'trailing_comma_in_multiline' => true,
'unary_operator_spaces' => true,
'yoda_style' => false,
// PSR standard to apply
'@PSR2' => true,
// PSR-12 rules; php-cs-fixer does not yet support PSR-12 natively
'compact_nullable_typehint' => true,
'declare_equal_normalize' => ['space' => "none"],
'function_typehint_space' => true,
'lowercase_cast' => true,
'lowercase_static_reference' => true,
'no_alternative_syntax' => true,
'no_empty_statement' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_whitespace_in_blank_line' => true,
'return_type_declaration' => ['space_before' => "none"],
'single_trait_insert_per_statement' => true,
'short_scalar_cast' => true,
'visibility_required' => ['elements' => ["const", "property", "method"]],
// house exceptions to PSR rules
'braces' => ['position_after_functions_and_oop_constructs' => "same"],
'function_declaration' => ['closure_function_spacing' => "none"],
'new_with_braces' => false, // no option to specify absence of braces
'new_with_braces' => [
'anonymous_class' => false,
'named_class' => false,
],
'single_blank_line_before_namespace' => false,
'blank_line_after_opening_tag' => false,
];
$finder = \PhpCsFixer\Finder::create();
@ -82,4 +72,4 @@ foreach ($paths as $path) {
$finder = $finder->in($path);
}
}
return \PhpCsFixer\Config::create()->setRiskyAllowed(true)->setRules($rules)->setFinder($finder);
return (new \PhpCsFixer\Config)->setRiskyAllowed(true)->setRules($rules)->setFinder($finder);

56
composer.lock

@ -58,16 +58,16 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "6.5.5",
"version": "6.5.6",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e"
"reference": "f092dd734083473658de3ee4bef093ed77d2689c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/9d4290de1cfd701f38099ef7e183b64b4b7b0c5e",
"reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/f092dd734083473658de3ee4bef093ed77d2689c",
"reference": "f092dd734083473658de3ee4bef093ed77d2689c",
"shasum": ""
},
"require": {
@ -104,10 +104,40 @@
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Jeremy Lindblom",
"email": "jeremeamia@gmail.com",
"homepage": "https://github.com/jeremeamia"
},
{
"name": "George Mponos",
"email": "gmponos@gmail.com",
"homepage": "https://github.com/gmponos"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://github.com/sagikazarmark"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle is a PHP HTTP client library",
@ -123,9 +153,23 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/6.5"
"source": "https://github.com/guzzle/guzzle/tree/6.5.6"
},
"time": "2020-06-16T21:01:06+00:00"
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
"type": "tidelift"
}
],
"time": "2022-05-25T13:19:12+00:00"
},
{
"name": "guzzlehttp/promises",

1
lib/Context/ExclusionMembers.php

@ -243,7 +243,6 @@ trait ExclusionMembers {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function markedRange($start = null, $end = null) {
if ($start === null && $end === null) {
$spec = null;

64
lib/Database.php

@ -280,7 +280,7 @@ class Database {
}
/** Renames a user
*
*
* This does not have an effect on their numeric ID, but has a cascading effect on many tables
*/
public function userRename(string $user, string $name): bool {
@ -337,7 +337,7 @@ class Database {
}
/** Retrieves any metadata associated with a user
*
*
* @param string $user The user whose metadata is to be retrieved
* @param bool $includeLarge Whether to include values which can be arbitrarily large text
*/
@ -813,8 +813,8 @@ class Database {
public function subscriptionList(string $user, $folder = null, bool $recursive = true, int $id = null): Db\Result {
// validate inputs
$folder = $this->folderValidateId($user, $folder)['id'];
// create a complex query
$integer = $this->db->sqlToken("integer");
// compile the query
$integerType = $this->db->sqlToken("integer");
$q = new Query(
"WITH RECURSIVE
topmost(f_id, top) as (
@ -835,7 +835,7 @@ class Database {
i.url as icon_url,
folder, t.top as top_folder, d.name as folder_name, dt.name as top_folder_name,
coalesce(s.title, f.title) as title,
coalesce((articles - hidden - marked), coalesce(articles,0)) as unread
cast(coalesce((articles - hidden - marked), coalesce(articles,0)) as $integerType) as unread -- this cast is required for MySQL for unclear reasons
from arsse_subscriptions as s
join arsse_feeds as f on f.id = s.feed
left join topmost as t on t.f_id = s.folder
@ -853,11 +853,11 @@ class Database {
select
subscription,
sum(hidden) as hidden,
sum(cast((\"read\" = 1 and hidden = 0) as $integer)) as marked
sum(case when \"read\" = 1 and hidden = 0 then 1 else 0 end) as marked
from arsse_marks group by subscription
) as mark_stats on mark_stats.subscription = s.id",
["str", "int"],
[$user, $folder]
["str", "int"],
[$user, $folder]
);
$q->setWhere("s.owner = ?", ["str"], [$user]);
$nocase = $this->db->sqlToken("nocase");
@ -1614,26 +1614,26 @@ class Database {
}
// ensure any used array-type context options contain at least one member
foreach ([
"articles",
"articles",
"editions",
"subscriptions",
"folders",
"foldersShallow",
"labels",
"labelNames",
"tags",
"tagNames",
"searchTerms",
"titleTerms",
"authorTerms",
"folders",
"foldersShallow",
"labels",
"labelNames",
"tags",
"tagNames",
"searchTerms",
"titleTerms",
"authorTerms",
"annotationTerms",
"modifiedRanges",
"markedRanges",
] as $m) {
if ($context->$m() && !$context->$m) {
throw new Db\ExceptionInput("tooShort", ['field' => $m, 'action' => $this->caller(), 'min' => 1]);
}
if ($context->$m() && !$context->$m) {
throw new Db\ExceptionInput("tooShort", ['field' => $m, 'action' => $this->caller(), 'min' => 1]);
}
}
// next compute the context, supplying the query to manipulate directly
$this->articleFilter($context, $q);
}
@ -1922,8 +1922,8 @@ class Database {
touched = 1
where
article in (select article from target_articles)
and subscription in (select distinct subscription from target_articles)",
[$subq->getTypes(), "bool"],
and subscription in (select distinct subscription from target_articles)",
[$subq->getTypes(), "bool"],
[$subq->getValues(), $data['read']]
);
$this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues());
@ -1953,7 +1953,7 @@ class Database {
where
article in (select article from target_articles)
and subscription in (select distinct subscription from target_articles)",
[$subq->getTypes(), $setTypes],
[$subq->getTypes(), $setTypes],
[$subq->getValues(), $setValues]
);
$this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues());
@ -2053,7 +2053,6 @@ class Database {
/** Deletes from the database articles which are beyond the configured clean-up threshold */
public function articleCleanup(): bool {
$integer = $this->db->sqlToken("integer");
$query = $this->db->prepareArray(
"WITH RECURSIVE
exempt_articles as (
@ -2079,8 +2078,8 @@ class Database {
left join (
select
article,
sum(cast((starred = 1 and hidden = 0) as $integer)) as starred,
sum(cast((\"read\" = 1 or hidden = 1) as $integer)) as \"read\",
sum(case when starred = 1 and hidden = 0 then 1 else 0 end) as starred,
sum(case when \"read\" = 1 or hidden = 1 then 1 else 0 end) as \"read\",
max(arsse_marks.modified) as marked_date
from arsse_marks
group by article
@ -2211,14 +2210,14 @@ class Database {
* @param boolean $includeEmpty Whether to include (true) or supress (false) labels which have no articles assigned to them
*/
public function labelList(string $user, bool $includeEmpty = true): Db\Result {
$integer = $this->db->sqlToken("integer");
$integerType = $this->db->sqlToken("integer");
return $this->db->prepareArray(
"SELECT * FROM (
SELECT
id,
name,
coalesce(articles - coalesce(hidden, 0), 0) as articles,
coalesce(marked, 0) as \"read\"
cast(coalesce(articles - coalesce(hidden, 0), 0) as $integerType) as articles, -- this cast is required for MySQL for unclear reasons
cast(coalesce(marked, 0) as $integerType) as \"read\" -- this cast is required for MySQL for unclear reasons
from arsse_labels
left join (
SELECT label, sum(assigned) as articles from arsse_label_members group by label
@ -2227,7 +2226,7 @@ class Database {
SELECT
label,
sum(hidden) as hidden,
sum(cast((\"read\" = 1 and hidden = 0) as $integer)) as marked
sum(case when \"read\" = 1 and hidden = 0 then 1 else 0 end) as marked
from arsse_marks
join arsse_subscriptions on arsse_subscriptions.id = arsse_marks.subscription
join arsse_label_members on arsse_label_members.article = arsse_marks.article
@ -2277,7 +2276,6 @@ class Database {
$this->labelValidateId($user, $id, $byName, false);
$field = $byName ? "name" : "id";
$type = $byName ? "str" : "int";
$integer = $this->db->sqlToken("integer");
$out = $this->db->prepareArray(
"SELECT
id,
@ -2292,7 +2290,7 @@ class Database {
SELECT
label,
sum(hidden) as hidden,
sum(cast((\"read\" = 1 and hidden = 0) as $integer)) as marked
sum(case when \"read\" = 1 and hidden = 0 then 1 else 0 end) as marked
from arsse_marks
join arsse_subscriptions on arsse_subscriptions.id = arsse_marks.subscription
join arsse_label_members on arsse_label_members.article = arsse_marks.article

4
lib/Db/AbstractDriver.php

@ -204,4 +204,8 @@ abstract class AbstractDriver implements Driver {
public function prepare(string $query, ...$paramType): Statement {
return $this->prepareArray($query, $paramType);
}
public function stringOutput(): bool {
return false;
}
}

4
lib/Db/Driver.php

@ -72,6 +72,7 @@ interface Driver {
* The tokens the implementation must understand are:
*
* - "greatest": the GREATEST function implemented by PostgreSQL and MySQL
* - "least": the LEAST function implemented by PostgreSQL and MySQL
* - "nocase": the name of a general-purpose case-insensitive collation sequence
* - "like": the case-insensitive LIKE operator
* - "integer": the integer type to use for explicit casts
@ -91,4 +92,7 @@ interface Driver {
* This should be restricted to quick maintenance; in SQLite terms it might include ANALYZE, but not VACUUM
*/
public function maintenance(): bool;
/** Reports whether the implementation will coerce integer and float values to text (string) */
public function stringOutput(): bool;
}

8
lib/Db/MySQL/Driver.php

@ -12,7 +12,7 @@ use JKingWeb\Arsse\Db\Exception;
class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
use ExceptionBuilder;
protected const SQL_MODE = "ANSI_QUOTES,HIGH_NOT_PRECEDENCE,NO_BACKSLASH_ESCAPES,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT,STRICT_ALL_TABLES";
protected const SQL_MODE = "ANSI_QUOTES,HIGH_NOT_PRECEDENCE,NO_BACKSLASH_ESCAPES,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT,STRICT_ALL_TABLES,NO_UNSIGNED_SUBTRACTION";
protected const TRANSACTIONAL_LOCKS = false;
/** @var \mysqli */
@ -81,10 +81,10 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
switch (strtolower($token)) {
case "nocase":
return '"utf8mb4_unicode_ci"';
case "integer":
return "signed integer";
case "asc":
return "";
case "integer":
return "signed integer";
default:
return $token;
}
@ -167,7 +167,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
$drv->report_mode = \MYSQLI_REPORT_OFF;
$this->db = mysqli_init();
$this->db->options(\MYSQLI_SET_CHARSET_NAME, "utf8mb4");
$this->db->options(\MYSQLI_OPT_INT_AND_FLOAT_NATIVE, false);
$this->db->options(\MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
$this->db->options(\MYSQLI_OPT_CONNECT_TIMEOUT, ceil(Arsse::$conf->dbTimeoutConnect));
@$this->db->real_connect($host, $user, $password, $db, $port, $socket);
if ($this->db->connect_errno) {

2
lib/Db/MySQL/PDODriver.php

@ -29,7 +29,7 @@ class PDODriver extends Driver {
try {
$this->db = new \PDO($dsn, $user, $password, [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_STRINGIFY_FETCHES => true,
\PDO::ATTR_STRINGIFY_FETCHES => false,
]);
} catch (\PDOException $e) {
$msg = $e->getMessage();

4
lib/Db/PostgreSQL/Driver.php

@ -232,4 +232,8 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
$this->exec("ANALYZE");
return true;
}
public function stringOutput(): bool {
return true;
}
}

4
lib/Db/PostgreSQL/PDODriver.php

@ -60,4 +60,8 @@ class PDODriver extends Driver {
public function prepareArray(string $query, array $paramTypes): \JKingWeb\Arsse\Db\Statement {
return new PDOStatement($this->db, $query, $paramTypes);
}
public function stringOutput(): bool {
return false;
}
}

1
lib/Db/PostgreSQL/PDOResult.php

@ -7,7 +7,6 @@ declare(strict_types=1);
namespace JKingWeb\Arsse\Db\PostgreSQL;
class PDOResult extends \JKingWeb\Arsse\Db\PDOResult {
// This method exists to transparent handle byte-array results
public function valid() {

4
lib/Db/SQLite3/PDODriver.php

@ -81,4 +81,8 @@ class PDODriver extends AbstractPDODriver {
}
}
}
public function stringOutput(): bool {
return true;
}
}

1
lib/Misc/URL.php

@ -10,7 +10,6 @@ namespace JKingWeb\Arsse\Misc;
* A collection of functions for manipulating URLs
*/
class URL {
/** Returns whether a URL is absolute i.e. has a scheme */
public static function absolute(string $url): bool {
return (bool) strlen((string) parse_url($url, \PHP_URL_SCHEME));

16
lib/Misc/ValueInfo.php

@ -107,7 +107,7 @@ class ValueInfo {
if ($strict && !$drop) {
throw new ExceptionType("strictFailure", $type);
}
return (!$drop) ? (int) $value->getTimestamp(): null;
return (!$drop) ? (int) $value->getTimestamp() : null;
} elseif ($value instanceof \DateInterval) {
if ($strict && !$drop) {
throw new ExceptionType("strictFailure", $type);
@ -159,7 +159,7 @@ class ValueInfo {
if ($strict && !$drop) {
throw new ExceptionType("strictFailure", $type);
}
return (!$drop) ? (float) $value->getTimestamp(): null;
return (!$drop) ? (float) $value->getTimestamp() : null;
} elseif ($value instanceof \DateInterval) {
if ($drop) {
return null;
@ -203,13 +203,13 @@ class ValueInfo {
if ($value->days) {
$dateSpec = $value->days."D";
} else {
$dateSpec .= $value->y ? $value->y."Y": "";
$dateSpec .= $value->m ? $value->m."M": "";
$dateSpec .= $value->d ? $value->d."D": "";
$dateSpec .= $value->y ? $value->y."Y" : "";
$dateSpec .= $value->m ? $value->m."M" : "";
$dateSpec .= $value->d ? $value->d."D" : "";
}
$timeSpec .= $value->h ? $value->h."H": "";
$timeSpec .= $value->i ? $value->i."M": "";
$timeSpec .= $value->s ? $value->s."S": "";
$timeSpec .= $value->h ? $value->h."H" : "";
$timeSpec .= $value->i ? $value->i."M" : "";
$timeSpec .= $value->s ? $value->s."S" : "";
$timeSpec = $timeSpec ? "T".$timeSpec : "";
if (!$dateSpec && !$timeSpec) {
return "PT0S";

4
lib/REST.php

@ -125,14 +125,14 @@ class REST {
$target = substr($url, strlen($api['strip']));
} else {
// if the match fails we are not able to handle the request
throw new REST\Exception501();
throw new REST\Exception501;
}
// return the API name, stripped URL, and API class name
return [$id, $target, $api['class']];
}
}
// or throw an exception otherwise
throw new REST\Exception501();
throw new REST\Exception501;
}
public function authenticateRequest(ServerRequestInterface $req): ServerRequestInterface {

2
lib/REST/TinyTinyRSS/Search.php

@ -319,7 +319,7 @@ class Search {
$start = $day."T00:00:00 $tz";
$end = $day."T23:59:59 $tz";
$cc = $neg ? $c->not : $c;
// NOTE: TTRSS treats multiple positive dates as contradictory; we instead treat them as complimentary instead, because it makes more sense
// NOTE: TTRSS treats multiple positive dates as contradictory; we instead treat them as complimentary instead, because it makes more sense
return $cc->modifiedRanges(array_merge($cc->modifiedRanges, [[$start, $end]]));
}

6
lib/Service.php

@ -21,13 +21,13 @@ class Service {
public function __construct() {
$driver = Arsse::$conf->serviceDriver;
$this->drv = new $driver();
$this->drv = new $driver;
}
public function watch(bool $loop = true): \DateTimeInterface {
$this->loop = $loop;
$this->signalInit();
$t = new \DateTime();
$t = new \DateTime;
do {
$this->checkIn();
static::cleanupPre();
@ -80,7 +80,7 @@ class Service {
// get the checking interval
$int = Arsse::$conf->serviceFrequency;
// subtract twice the checking interval from the current time to yield the earliest acceptable check-in time
$limit = new \DateTime();
$limit = new \DateTime;
$limit->sub($int);
$limit->sub($int);
// return whether the check-in time is within the acceptable limit

124
tests/cases/Database/SeriesArticle.php

@ -18,11 +18,7 @@ trait SeriesArticle {
protected function setUpSeriesArticle(): void {
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
],
'columns' => ["id", "password", "num"],
'rows' => [
["jane.doe@example.com", "", 1],
["john.doe@example.com", "", 2],
@ -32,11 +28,7 @@ trait SeriesArticle {
],
],
'arsse_feeds' => [
'columns' => [
'id' => "int",
'url' => "str",
'title' => "str",
],
'columns' => ["id", "url", "title"],
'rows' => [
[1,"http://example.com/1", "Feed 1"],
[2,"http://example.com/2", "Feed 2"],
@ -54,12 +46,7 @@ trait SeriesArticle {
],
],
'arsse_folders' => [
'columns' => [
'id' => "int",
'owner' => "str",
'parent' => "int",
'name' => "str",
],
'columns' => ["id", "owner", "parent", "name"],
'rows' => [
[1, "john.doe@example.com", null, "Technology"],
[2, "john.doe@example.com", 1, "Software"],
@ -73,11 +60,7 @@ trait SeriesArticle {
],
],
'arsse_tags' => [
'columns' => [
'id' => "int",
'owner' => "str",
'name' => "str",
],
'columns' => ["id", "owner", "name"],
'rows' => [
[1, "john.doe@example.com", "Technology"],
[2, "john.doe@example.com", "Software"],
@ -90,38 +73,27 @@ trait SeriesArticle {
],
],
'arsse_subscriptions' => [
'columns' => [
'id' => "int",
'owner' => "str",
'feed' => "int",
'folder' => "int",
'title' => "str",
'scrape' => "bool",
],
'columns' => ["id", "owner", "feed", "folder", "title", "scrape"],
'rows' => [
[1, "john.doe@example.com",1, null,"Subscription 1",0],
[2, "john.doe@example.com",2, null,null,0],
[3, "john.doe@example.com",3, 1,"Subscription 3",0],
[4, "john.doe@example.com",4, 6,null,0],
[5, "john.doe@example.com",10, 5,"Subscription 5",0],
[6, "jane.doe@example.com",1, null,null,0],
[7, "jane.doe@example.com",10,null,"Subscription 7",0],
[8, "john.doe@example.org",11,null,null,0],
[9, "john.doe@example.org",12,null,"Subscription 9",0],
[10,"john.doe@example.org",13,null,null,0],
[1, "john.doe@example.com",1, null,"Subscription 1", 0],
[2, "john.doe@example.com",2, null,null, 0],
[3, "john.doe@example.com",3, 1,"Subscription 3", 0],
[4, "john.doe@example.com",4, 6,null, 0],
[5, "john.doe@example.com",10, 5,"Subscription 5", 0],
[6, "jane.doe@example.com",1, null,null, 0],
[7, "jane.doe@example.com",10,null,"Subscription 7", 0],
[8, "john.doe@example.org",11,null,null, 0],
[9, "john.doe@example.org",12,null,"Subscription 9", 0],
[10,"john.doe@example.org",13,null,null, 0],
[11,"john.doe@example.net",10,null,"Subscription 11",0],
[12,"john.doe@example.net",2, 9,null,0],
[12,"john.doe@example.net",2, 9,null, 0],
[13,"john.doe@example.net",3, 8,"Subscription 13",0],
[14,"john.doe@example.net",4, 7,null,0],
[15,"jill.doe@example.com",11,null,null,1],
[14,"john.doe@example.net",4, 7,null, 0],
[15,"jill.doe@example.com",11,null,null, 1],
],
],
'arsse_tag_members' => [
'columns' => [
'tag' => "int",
'subscription' => "int",
'assigned' => "bool",
],
'columns' => ["tag", "subscription", "assigned"],
'rows' => [
[1,3,1],
[1,4,1],
@ -137,20 +109,8 @@ trait SeriesArticle {
],
'arsse_articles' => [
'columns' => [
'id' => "int",
'feed' => "int",
'url' => "str",
'title' => "str",
'author' => "str",
'published' => "datetime",
'edited' => "datetime",
'content' => "str",
'guid' => "str",
'url_title_hash' => "str",
'url_content_hash' => "str",
'title_content_hash' => "str",
'modified' => "datetime",
'content_scraped' => "str",
"id", "feed", "url", "title", "author", "published", "edited", "content", "guid",
"url_title_hash", "url_content_hash", "title_content_hash", "modified", "content_scraped"
],
'rows' => [
[1,1,null,"Title one", null,null,null,"First article", null,"","","","2000-01-01T00:00:00Z",null],
@ -181,11 +141,7 @@ trait SeriesArticle {
],
],
'arsse_enclosures' => [
'columns' => [
'article' => "int",
'url' => "str",
'type' => "str",
],
'columns' => ["article", "url", "type"],
'rows' => [
[102,"http://example.com/text","text/plain"],
[103,"http://example.com/video","video/webm"],
@ -195,10 +151,7 @@ trait SeriesArticle {
],
],
'arsse_editions' => [
'columns' => [
'id' => "int",
'article' => "int",
],
'columns' => ["id", "article"],
'rows' => [
[1,1],
[2,2],
@ -234,15 +187,7 @@ trait SeriesArticle {
],
],
'arsse_marks' => [
'columns' => [
'subscription' => "int",
'article' => "int",
'read' => "bool",
'starred' => "bool",
'modified' => "datetime",
'note' => "str",
'hidden' => "bool",
],
'columns' => ["subscription", "article", "read", "starred", "modified", "note", "hidden"],
'rows' => [
[1, 1,1,1,'2000-01-01 00:00:00','',0],
[5, 19,1,0,'2016-01-01 00:00:00','',0],
@ -263,10 +208,7 @@ trait SeriesArticle {
],
],
'arsse_categories' => [ // author-supplied categories
'columns' => [
'article' => "int",
'name' => "str",
],
'columns' => ["article", "name"],
'rows' => [
[19,"Fascinating"],
[19,"Logical"],
@ -274,12 +216,8 @@ trait SeriesArticle {
[20,"Logical"],
],
],
'arsse_labels' => [
'columns' => [
'id' => "int",
'owner' => "str",
'name' => "str",
],
'arsse_labels' => [ // labels applied to articles
'columns' => ["id", "owner", "name"],
'rows' => [
[1,"john.doe@example.com","Interesting"],
[2,"john.doe@example.com","Fascinating"],
@ -288,13 +226,7 @@ trait SeriesArticle {
],
],
'arsse_label_members' => [
'columns' => [
'label' => "int",
'article' => "int",
'subscription' => "int",
'assigned' => "bool",
'modified' => "datetime",
],
'columns' => ["label", "article", "subscription", "assigned", "modified"],
'rows' => [
[1, 1,1,1,'2000-01-01 00:00:00'],
[2, 1,1,1,'2000-01-01 00:00:00'],

64
tests/cases/Database/SeriesCleanup.php

@ -27,23 +27,14 @@ trait SeriesCleanup {
$faroff = (new Date("now + 1 hour", $tz))->format("Y-m-d H:i:s");
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
],
'columns' => ["id", "password", "num"],
'rows' => [
["jane.doe@example.com", "",1],
["john.doe@example.com", "",2],
],
],
'arsse_sessions' => [
'columns' => [
'id' => "str",
'created' => "datetime",
'expires' => "datetime",
'user' => "str",
],
'columns' => ["id", "created", "expires", "user"],
'rows' => [
["a", $nowish, $faroff, "jane.doe@example.com"], // not expired and recently created, thus kept
["b", $nowish, $soon, "jane.doe@example.com"], // not expired and recently created, thus kept
@ -53,12 +44,7 @@ trait SeriesCleanup {
],
],
'arsse_tokens' => [
'columns' => [
'id' => "str",
'class' => "str",
'user' => "str",
'expires' => "datetime",
],
'columns' => ["id", "class", "user", "expires"],
'rows' => [
["80fa94c1a11f11e78667001e673b2560", "fever.login", "jane.doe@example.com", $faroff],
["27c6de8da13311e78667001e673b2560", "fever.login", "jane.doe@example.com", $weeksago], // expired
@ -67,11 +53,7 @@ trait SeriesCleanup {
],
],
'arsse_icons' => [
'columns' => [
'id' => "int",
'url' => "str",
'orphaned' => "datetime",
],
'columns' => ["id", "url", "orphaned"],
'rows' => [
[1,'http://localhost:8000/Icon/PNG',$daybefore],
[2,'http://localhost:8000/Icon/GIF',$daybefore],
@ -79,14 +61,7 @@ trait SeriesCleanup {
],
],
'arsse_feeds' => [
'columns' => [
'id' => "int",
'url' => "str",
'title' => "str",
'orphaned' => "datetime",
'size' => "int",
'icon' => "int",
],
'columns' => ["id", "url", "title", "orphaned", "size", "icon"],
'rows' => [
[1,"http://example.com/1","",$daybefore,2,null], //latest two articles should be kept
[2,"http://example.com/2","",$yesterday,0,2],
@ -95,11 +70,7 @@ trait SeriesCleanup {
],
],
'arsse_subscriptions' => [
'columns' => [
'id' => "int",
'owner' => "str",
'feed' => "int",
],
'columns' => ["id", "owner", "feed"],
'rows' => [
// one feed previously marked for deletion has a subscription again, and so should not be deleted
[1,'jane.doe@example.com',1],
@ -108,14 +79,7 @@ trait SeriesCleanup {
],
],
'arsse_articles' => [
'columns' => [
'id' => "int",
'feed' => "int",
'url_title_hash' => "str",
'url_content_hash' => "str",
'title_content_hash' => "str",
'modified' => "datetime",
],
'columns' => ["id", "feed", "url_title_hash", "url_content_hash", "title_content_hash", "modified"],
'rows' => [
[1,1,"","","",$weeksago], // is the latest article, thus is kept
[2,1,"","","",$weeksago], // is the second latest article, thus is kept
@ -129,10 +93,7 @@ trait SeriesCleanup {
],
],
'arsse_editions' => [
'columns' => [
'id' => "int",
'article' => "int",
],
'columns' => ["id", "article"],
'rows' => [
[1,1],
[2,2],
@ -143,14 +104,7 @@ trait SeriesCleanup {
],
],
'arsse_marks' => [
'columns' => [
'article' => "int",
'subscription' => "int",
'read' => "bool",
'starred' => "bool",
'hidden' => "bool",
'modified' => "datetime",
],
'columns' => ["article", "subscription", "read", "starred", "hidden", "modified"],
'rows' => [
[3,1,0,1,0,$weeksago],
[4,1,1,0,0,$daysago],

75
tests/cases/Database/SeriesFeed.php

@ -17,23 +17,14 @@ trait SeriesFeed {
$now = gmdate("Y-m-d H:i:s", strtotime("now"));
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
],
'columns' => ["id", "password", "num"],
'rows' => [
["jane.doe@example.com", "",1],
["john.doe@example.com", "",2],
],
],
'arsse_icons' => [
'columns' => [
'id' => "int",
'url' => "str",
'type' => "str",
'data' => "blob",
],
'columns' => ["id", "url", "type", "data"],
'rows' => [
[1,'http://localhost:8000/Icon/PNG','image/png',base64_decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAAADUlEQVQYV2NgYGBgAAAABQABijPjAAAAAABJRU5ErkJggg==")],
[2,'http://localhost:8000/Icon/GIF','image/gif',base64_decode("R0lGODlhAQABAIABAAAAAP///yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==")],
@ -42,17 +33,7 @@ trait SeriesFeed {
],
],
'arsse_feeds' => [
'columns' => [
'id' => "int",
'url' => "str",
'title' => "str",
'err_count' => "int",
'err_msg' => "str",
'modified' => "datetime",
'next_fetch' => "datetime",
'size' => "int",
'icon' => "int",
],
'columns' => ["id", "url", "title", "err_count", "err_msg", "modified", "next_fetch", "size", "icon"],
'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],
@ -67,13 +48,7 @@ trait SeriesFeed {
],
],
'arsse_subscriptions' => [
'columns' => [
'id' => "int",
'owner' => "str",
'feed' => "int",
'keep_rule' => "str",
'block_rule' => "str",
],
'columns' => ["id", "owner", "feed", "keep_rule", "block_rule"],
'rows' => [
[1,'john.doe@example.com',1,null,'^Sport$'],
[2,'john.doe@example.com',2,"",null],
@ -84,21 +59,7 @@ trait SeriesFeed {
],
],
'arsse_articles' => [
'columns' => [
'id' => "int",
'feed' => "int",
'url' => "str",
'title' => "str",
'author' => "str",
'published' => "datetime",
'edited' => "datetime",
'content' => "str",
'guid' => "str",
'url_title_hash' => "str",
'url_content_hash' => "str",
'title_content_hash' => "str",
'modified' => "datetime",
],
'columns' => ["id", "feed", "url", "title", "author", "published", "edited", "content", "guid", "url_title_hash", "url_content_hash", "title_content_hash", "modified"],
'rows' => [
[1,1,'http://example.com/1','Article title 1','','2000-01-01 00:00:00','2000-01-01 00:00:00','<p>Article content 1</p>','e433653cef2e572eee4215fa299a4a5af9137b2cefd6283c85bd69a32915beda','f5cb8bfc1c7396dc9816af212a3e2ac5221585c2a00bf7ccb6aabd95dcfcd6a6','fb0bc8f8cb08913dc5a497db700e327f1d34e4987402687d494a5891f24714d4','18fdd4fa93d693128c43b004399e5c9cea6c261ddfa002518d3669f55d8c2207',$past],
[2,1,'http://example.com/2','Article title 2','','2000-01-02 00:00:00','2000-01-02 00:00:00','<p>Article content 2</p>','5be8a5a46ecd52ed132191c8d27fb1af6b3d4edc00234c5d9f8f0e10562ed3b7','0e86d2de822a174fe3c44a466953e63ca1f1a58a19cbf475fce0855d4e3d5153','13075894189c47ffcfafd1dfe7fbb539f7c74a69d35a399b3abf8518952714f9','2abd0a8cba83b8214a66c8f0293ba63e467d720540e29ff8ddcdab069d4f1c9e',$past],
@ -110,11 +71,7 @@ trait SeriesFeed {
],
],
'arsse_editions' => [
'columns' => [
'id' => "int",
'article' => "int",
'modified' => "datetime",
],
'columns' => ["id", "article", "modified"],
'rows' => [
[1,1,$past],
[2,2,$past],
@ -124,14 +81,7 @@ trait SeriesFeed {
],
],
'arsse_marks' => [
'columns' => [
'article' => "int",
'subscription' => "int",
'read' => "bool",
'starred' => "bool",
'hidden' => "bool",
'modified' => "datetime",
],
'columns' => ["article", "subscription", "read", "starred", "hidden", "modified"],
'rows' => [
// Jane's marks
[1,6,1,0,0,$past],
@ -146,20 +96,13 @@ trait SeriesFeed {
],
],
'arsse_enclosures' => [
'columns' => [
'article' => "int",
'url' => "str",
'type' => "str",
],
'columns' => ["article", "url", "type"],
'rows' => [
[7,'http://example.com/png','image/png'],
],
],
'arsse_categories' => [
'columns' => [
'article' => "int",
'name' => "str",
],
'columns' => ["article", "name"],
'rows' => [
[7,'Syrinx'],
],

26
tests/cases/Database/SeriesFolder.php

@ -12,23 +12,14 @@ trait SeriesFolder {
protected function setUpSeriesFolder(): void {
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
],
'columns' => ["id", "password", "num"],
'rows' => [
["jane.doe@example.com", "",1],
["john.doe@example.com", "",2],
],
],
'arsse_folders' => [
'columns' => [
'id' => "int",
'owner' => "str",
'parent' => "int",
'name' => "str",
],
'columns' => ["id", "owner", "parent", "name"],
/* Layout translates to:
Jane
Politics
@ -49,11 +40,7 @@ trait SeriesFolder {
],
],
'arsse_feeds' => [
'columns' => [
'id' => "int",
'url' => "str",
'title' => "str",
],
'columns' => ["id", "url", "title"],
'rows' => [
[1,"http://example.com/1", "Feed 1"],
[2,"http://example.com/2", "Feed 2"],
@ -71,12 +58,7 @@ trait SeriesFolder {
],
],
'arsse_subscriptions' => [
'columns' => [
'id' => "int",
'owner' => "str",
'feed' => "int",
'folder' => "int",
],
'columns' => ["id", "owner", "feed", "folder"],
'rows' => [
[1, "john.doe@example.com",1, null],
[2, "john.doe@example.com",2, null],

31
tests/cases/Database/SeriesIcon.php

@ -16,23 +16,14 @@ trait SeriesIcon {
$now = gmdate("Y-m-d H:i:s", strtotime("now"));
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
],
'columns' => ["id", "password", "num"],
'rows' => [
["jane.doe@example.com", "",1],
["john.doe@example.com", "",2],
],
],
'arsse_icons' => [
'columns' => [
'id' => "int",
'url' => "str",
'type' => "str",
'data' => "blob",
],
'columns' => ["id", "url", "type", "data"],
'rows' => [
[1,'http://localhost:8000/Icon/PNG','image/png',base64_decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAAADUlEQVQYV2NgYGBgAAAABQABijPjAAAAAABJRU5ErkJggg==")],
[2,'http://localhost:8000/Icon/GIF','image/gif',base64_decode("R0lGODlhAQABAIABAAAAAP///yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==")],
@ -41,17 +32,7 @@ trait SeriesIcon {
],
],
'arsse_feeds' => [
'columns' => [
'id' => "int",
'url' => "str",
'title' => "str",
'err_count' => "int",
'err_msg' => "str",
'modified' => "datetime",
'next_fetch' => "datetime",
'size' => "int",
'icon' => "int",
],
'columns' => ["id", "url", "title", "err_count", "err_msg", "modified", "next_fetch", "size", "icon"],
'rows' => [
[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],
@ -61,11 +42,7 @@ trait SeriesIcon {
],
],
'arsse_subscriptions' => [
'columns' => [
'id' => "int",
'owner' => "str",
'feed' => "int",
],
'columns' => ["id", "owner", "feed"],
'rows' => [
[1,'john.doe@example.com',1],
[2,'john.doe@example.com',2],

74
tests/cases/Database/SeriesLabel.php

@ -14,11 +14,7 @@ trait SeriesLabel {
protected function setUpSeriesLabel(): void {
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
],
'columns' => ["id", "password", "num"],
'rows' => [
["jane.doe@example.com", "",1],
["john.doe@example.com", "",2],
@ -27,12 +23,7 @@ trait SeriesLabel {
],
],
'arsse_folders' => [
'columns' => [
'id' => "int",
'owner' => "str",
'parent' => "int",
'name' => "str",
],
'columns' => ["id", "owner", "parent", "name"],
'rows' => [
[1, "john.doe@example.com", null, "Technology"],
[2, "john.doe@example.com", 1, "Software"],
@ -46,10 +37,7 @@ trait SeriesLabel {
],
],
'arsse_feeds' => [
'columns' => [
'id' => "int",
'url' => "str",
],
'columns' => ["id", "url"],
'rows' => [
[1,"http://example.com/1"],
[2,"http://example.com/2"],
@ -67,12 +55,7 @@ trait SeriesLabel {
],
],
'arsse_subscriptions' => [
'columns' => [
'id' => "int",
'owner' => "str",
'feed' => "int",
'folder' => "int",
],
'columns' => ["id", "owner", "feed", "folder"],
'rows' => [
[1,"john.doe@example.com",1,null],
[2,"john.doe@example.com",2,null],
@ -91,21 +74,7 @@ trait SeriesLabel {
],
],
'arsse_articles' => [
'columns' => [
'id' => "int",
'feed' => "int",
'url' => "str",
'title' => "str",
'author' => "str",
'published' => "datetime",
'edited' => "datetime",
'content' => "str",
'guid' => "str",
'url_title_hash' => "str",
'url_content_hash' => "str",
'title_content_hash' => "str",
'modified' => "datetime",
],
'columns' => ["id", "feed", "url", "title", "author", "published", "edited", "content", "guid", "url_title_hash", "url_content_hash", "title_content_hash", "modified"],
'rows' => [
[1,1,null,null,null,null,null,null,null,"","","","2000-01-01T00:00:00Z"],
[2,1,null,null,null,null,null,null,null,"","","","2010-01-01T00:00:00Z"],
@ -135,11 +104,7 @@ trait SeriesLabel {
],
],
'arsse_enclosures' => [
'columns' => [
'article' => "int",
'url' => "str",
'type' => "str",
],
'columns' => ["article", "url", "type"],
'rows' => [
[102,"http://example.com/text","text/plain"],
[103,"http://example.com/video","video/webm"],
@ -149,10 +114,7 @@ trait SeriesLabel {
],
],
'arsse_editions' => [
'columns' => [
'id' => "int",
'article' => "int",
],
'columns' => ["id", "article"],
'rows' => [
[1,1],
[2,2],
@ -188,14 +150,7 @@ trait SeriesLabel {
],
],
'arsse_marks' => [
'columns' => [
'subscription' => "int",
'article' => "int",
'read' => "bool",
'starred' => "bool",
'modified' => "datetime",
'hidden' => "bool",
],
'columns' => ["subscription", "article", "read", "starred", "modified", "hidden"],
'rows' => [
[1, 1,1,1,'2000-01-01 00:00:00',0],
[5, 19,1,0,'2000-01-01 00:00:00',0],
@ -213,11 +168,7 @@ trait SeriesLabel {
],
],
'arsse_labels' => [
'columns' => [
'id' => "int",
'owner' => "str",
'name' => "str",
],
'columns' => ["id", "owner", "name"],
'rows' => [
[1,"john.doe@example.com","Interesting"],
[2,"john.doe@example.com","Fascinating"],
@ -226,12 +177,7 @@ trait SeriesLabel {
],
],
'arsse_label_members' => [
'columns' => [
'label' => "int",
'article' => "int",
'subscription' => "int",
'assigned' => "bool",
],
'columns' => ["label", "article", "subscription", "assigned"],
'rows' => [
[1, 1,1,1],
[2, 1,1,1],

9
tests/cases/Database/SeriesMeta.php

@ -13,13 +13,10 @@ trait SeriesMeta {
protected function setUpSeriesMeta(): void {
$dataBare = [
'arsse_meta' => [
'columns' => [
'key' => 'str',
'value' => 'str',
],
'columns' => ["key", "value"],
'rows' => [
//['schema_version', "".\JKingWeb\Arsse\Database::SCHEMA_VERSION],
['album',"A Farewell to Kings"],
//['schema_version', "".\JKingWeb\Arsse\Database::SCHEMA_VERSION],
['album',"A Farewell to Kings"],
],
],
];

13
tests/cases/Database/SeriesSession.php

@ -23,23 +23,14 @@ trait SeriesSession {
$old = gmdate("Y-m-d H:i:s", strtotime("now - 2 days"));
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
],
'columns' => ["id", "password", "num"],
'rows' => [
["jane.doe@example.com", "",1],
["john.doe@example.com", "",2],
],
],
'arsse_sessions' => [
'columns' => [
'id' => "str",
'user' => "str",
'created' => "datetime",
'expires' => "datetime",
],
'columns' => ["id", "user", "created", "expires"],
'rows' => [
["80fa94c1a11f11e78667001e673b2560", "jane.doe@example.com", $past, $faroff],
["27c6de8da13311e78667001e673b2560", "jane.doe@example.com", $past, $past], // expired

82
tests/cases/Database/SeriesSubscription.php

@ -15,11 +15,7 @@ trait SeriesSubscription {
public function setUpSeriesSubscription(): void {
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
],
'columns' => ["id", "password", "num"],
'rows' => [
["jane.doe@example.com", "", 1],
["john.doe@example.com", "", 2],
@ -28,12 +24,7 @@ trait SeriesSubscription {
],
],
'arsse_folders' => [
'columns' => [
'id' => "int",
'owner' => "str",
'parent' => "int",
'name' => "str",
],
'columns' => ["id", "owner", "parent", "name"],
'rows' => [
[1, "john.doe@example.com", null, "Technology"],
[2, "john.doe@example.com", 1, "Software"],
@ -44,27 +35,14 @@ trait SeriesSubscription {
],
],
'arsse_icons' => [
'columns' => [
'id' => "int",
'url' => "str",
'data' => "blob",
],
'columns' => ["id", "url", "data"],
'rows' => [
[1,"http://example.com/favicon.ico", "ICON DATA"],
[2,"http://example.net/favicon.ico", null],
],
],
'arsse_feeds' => [
'columns' => [
'id' => "int",
'url' => "str",
'title' => "str",
'username' => "str",
'password' => "str",
'updated' => "datetime",
'next_fetch' => "datetime",
'icon' => "int",
],
'columns' => ["id", "url", "title", "username", "password", "updated", "next_fetch", "icon"],
'rows' => [
[1,"http://example.com/feed1", "Ook", "", "",strtotime("now"),strtotime("now"),null],
[2,"http://example.com/feed2", "eek", "", "",strtotime("now - 1 hour"),strtotime("now - 1 hour"),1],
@ -73,18 +51,7 @@ trait SeriesSubscription {
],
],
'arsse_subscriptions' => [
'columns' => [
'id' => "int",
'owner' => "str",
'feed' => "int",
'title' => "str",
'folder' => "int",
'pinned' => "bool",
'order_type' => "int",
'keep_rule' => "str",
'block_rule' => "str",
'scrape' => "bool",
],
'columns' => ["id", "owner", "feed", "title", "folder", "pinned", "order_type", "keep_rule", "block_rule", "scrape"],
'rows' => [
[1,"john.doe@example.com",2,null,null,1,2,null,null,0],
[2,"jane.doe@example.com",2,null,null,0,0,null,null,0],
@ -95,11 +62,7 @@ trait SeriesSubscription {
],
],
'arsse_tags' => [
'columns' => [
'id' => "int",
'owner' => "str",
'name' => "str",
],
'columns' => ["id", "owner", "name"],
'rows' => [
[1,"john.doe@example.com","Interesting"],
[2,"john.doe@example.com","Fascinating"],
@ -108,11 +71,7 @@ trait SeriesSubscription {
],
],
'arsse_tag_members' => [
'columns' => [
'tag' => "int",
'subscription' => "int",
'assigned' => "bool",
],
'columns' => ["tag", "subscription", "assigned"],
'rows' => [
[1,1,1],
[1,3,0],
@ -122,14 +81,7 @@ trait SeriesSubscription {
],
],
'arsse_articles' => [
'columns' => [
'id' => "int",
'feed' => "int",
'url_title_hash' => "str",
'url_content_hash' => "str",
'title_content_hash' => "str",
'title' => "str",
],
'columns' => ["id", "feed", "url_title_hash", "url_content_hash", "title_content_hash", "title"],
'rows' => [
[1,2,"","","","Title 1"],
[2,2,"","","","Title 2"],
@ -142,10 +94,7 @@ trait SeriesSubscription {
],
],
'arsse_editions' => [
'columns' => [
'id' => "int",
'article' => "int",
],
'columns' => ["id", "article"],
'rows' => [
[1,1],
[2,2],
@ -158,10 +107,7 @@ trait SeriesSubscription {
],
],
'arsse_categories' => [
'columns' => [
'article' => "int",
'name' => "str",
],
'columns' => ["article", "name"],
'rows' => [
[1,"A"],
[2,"B"],
@ -173,13 +119,7 @@ trait SeriesSubscription {
],
],
'arsse_marks' => [
'columns' => [
'article' => "int",
'subscription' => "int",
'read' => "bool",
'starred' => "bool",
'hidden' => "bool",
],
'columns' => ["article", "subscription", "read", "starred", "hidden"],
'rows' => [
[1,2,1,0,0],
[2,2,1,0,0],

31
tests/cases/Database/SeriesTag.php

@ -13,11 +13,7 @@ trait SeriesTag {
protected function setUpSeriesTag(): void {
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
],
'columns' => ["id", "password", "num"],
'rows' => [
["jane.doe@example.com", "",1],
["john.doe@example.com", "",2],
@ -26,11 +22,7 @@ trait SeriesTag {
],
],
'arsse_feeds' => [
'columns' => [
'id' => "int",
'url' => "str",
'title' => "str",
],
'columns' => ["id", "url", "title"],
'rows' => [
[1,"http://example.com/1",""],
[2,"http://example.com/2",""],
@ -48,12 +40,7 @@ trait SeriesTag {
],
],
'arsse_subscriptions' => [
'columns' => [
'id' => "int",
'owner' => "str",
'feed' => "int",
'title' => "str",
],
'columns' => ["id", "owner", "feed", "title"],
'rows' => [
[1, "john.doe@example.com", 1,"Lord of Carrots"],
[2, "john.doe@example.com", 2,null],
@ -72,11 +59,7 @@ trait SeriesTag {
],
],
'arsse_tags' => [
'columns' => [
'id' => "int",
'owner' => "str",
'name' => "str",
],
'columns' => ["id", "owner", "name"],
'rows' => [
[1,"john.doe@example.com","Interesting"],
[2,"john.doe@example.com","Fascinating"],
@ -85,11 +68,7 @@ trait SeriesTag {
],
],
'arsse_tag_members' => [
'columns' => [
'tag' => "int",
'subscription' => "int",
'assigned' => "bool",
],
'columns' => ["tag", "subscription", "assigned"],
'rows' => [
[1,1,1],
[1,3,0],

14
tests/cases/Database/SeriesToken.php

@ -17,24 +17,14 @@ trait SeriesToken {
$old = gmdate("Y-m-d H:i:s", strtotime("now - 2 days"));
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
],
'columns' => ["id", "password", "num"],
'rows' => [
["jane.doe@example.com", "",1],
["john.doe@example.com", "",2],
],
],
'arsse_tokens' => [
'columns' => [
'id' => "str",
'class' => "str",
'user' => "str",
'expires' => "datetime",
'data' => "str",
],
'columns' => ["id", "class", "user", "expires", "data"],
'rows' => [
["80fa94c1a11f11e78667001e673b2560", "fever.login", "jane.doe@example.com", $faroff, null],
["27c6de8da13311e78667001e673b2560", "fever.login", "jane.doe@example.com", $past, null], // expired

13
tests/cases/Database/SeriesUser.php

@ -12,12 +12,7 @@ trait SeriesUser {
protected function setUpSeriesUser(): void {
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
'admin' => 'bool',
],
'columns' => ["id", "password", "num", "admin"],
'rows' => [
["admin@example.net", '$2y$10$PbcG2ZR3Z8TuPzM7aHTF8.v61dtCjzjK78gdZJcp4UePE8T9jEgBW', 1, 1], // password is hash of "secret"
["jane.doe@example.com", "", 2, 0],
@ -25,11 +20,7 @@ trait SeriesUser {
],
],
'arsse_user_meta' => [
'columns' => [
'owner' => "str",
'key' => "str",
'value' => "str",
],
'columns' => ["owner", "key", "value"],
'rows' => [
["admin@example.net", "lang", "en"],
["admin@example.net", "tz", "America/Toronto"],

2
tests/cases/Exception/TestException.php

@ -31,7 +31,7 @@ class TestException extends \JKingWeb\Arsse\Test\AbstractTest {
*/
public function testBaseClassWithoutMessage(): void {
$this->assertException("unknown");
throw new Exception();
throw new Exception;
}
/**

8
tests/cases/Feed/TestException.php

@ -150,10 +150,10 @@ class TestException extends \JKingWeb\Arsse\Test\AbstractTest {
public function providePicoFeedException() {
return [
'Failed feed discovery' => [new \PicoFeed\Reader\SubscriptionNotFoundException(), "subscriptionNotFound"],
'Unsupported format' => [new \PicoFeed\Reader\UnsupportedFeedFormatException(), "unsupportedFeedFormat"],
'Malformed XML' => [new \PicoFeed\Parser\MalformedXmlException(), "malformedXml"],
'XML entity expansion' => [new \PicoFeed\Parser\XmlEntityException(), "xmlEntity"],
'Failed feed discovery' => [new \PicoFeed\Reader\SubscriptionNotFoundException, "subscriptionNotFound"],
'Unsupported format' => [new \PicoFeed\Reader\UnsupportedFeedFormatException, "unsupportedFeedFormat"],
'Malformed XML' => [new \PicoFeed\Parser\MalformedXmlException, "malformedXml"],
'XML entity expansion' => [new \PicoFeed\Parser\XmlEntityException, "xmlEntity"],
];
}

39
tests/cases/ImportExport/TestImportExport.php

@ -41,23 +41,14 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
Arsse::$db->driverSchemaUpdate();
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
],
'columns' => ["id", "password", "num"],
'rows' => [
["john.doe@example.com", "", 1],
["jane.doe@example.com", "", 2],
],
],
'arsse_folders' => [
'columns' => [
'id' => "int",
'owner' => "str",
'parent' => "int",
'name' => "str",
],
'columns' => ["id", "owner", "parent", "name"],
'rows' => [
[1, "john.doe@example.com", null, "Science"],
[2, "john.doe@example.com", 1, "Rocketry"],
@ -68,11 +59,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
],
],
'arsse_feeds' => [
'columns' => [
'id' => "int",
'url' => "str",
'title' => "str",
],
'columns' => ["id", "url", "title"],
'rows' => [
[1, "http://localhost:8000/Import/nasa-jpl", "NASA JPL"],
[2, "http://localhost:8000/Import/torstar", "Toronto Star"],
@ -83,13 +70,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
],
],
'arsse_subscriptions' => [
'columns' => [
'id' => "int",
'owner' => "str",
'folder' => "int",
'feed' => "int",
'title' => "str",
],
'columns' => ["id", "owner", "folder", "feed", "title"],
'rows' => [
[1, "john.doe@example.com", 2, 1, "NASA JPL"],
[2, "john.doe@example.com", 5, 2, "Toronto Star"],
@ -100,11 +81,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
],
],
'arsse_tags' => [
'columns' => [
'id' => "int",
'owner' => "str",
'name' => "str",
],
'columns' => ["id", "owner", "name"],
'rows' => [
[1, "john.doe@example.com", "canada"],
[2, "john.doe@example.com", "frequent"],
@ -115,11 +92,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
],
],
'arsse_tag_members' => [
'columns' => [
'tag' => "int",
'subscription' => "int",
'assigned' => "bool",
],
'columns' => ["tag", "subscription", "assigned"],
'rows' => [
[1, 2, 1],
[1, 4, 1],

8
tests/cases/Misc/TestContext.php

@ -93,7 +93,7 @@ class TestContext extends \JKingWeb\Arsse\Test\AbstractTest {
'articleRange' => [[1, 100], [1, 100]],
'editionRange' => [[1, 100], [1, 100]],
];
foreach($tests as $k => $t) {
foreach ($tests as $k => $t) {
yield $k => array_merge([$k], $t, [false]);
if (method_exists(ExclusionContext::class, $k)) {
yield "$k (not)" => array_merge([$k], $t, [true]);
@ -103,7 +103,7 @@ class TestContext extends \JKingWeb\Arsse\Test\AbstractTest {
public function testCleanIdArrayValues(): void {
$methods = ["articles", "editions", "tags", "labels", "subscriptions"];
$in = [1, "2", 3.5, 4.0, 4, "ook", 0, -20, true, false, null, new \DateTime(), -1.0];
$in = [1, "2", 3.5, 4.0, 4, "ook", 0, -20, true, false, null, new \DateTime, -1.0];
$out = [1, 2, 4];
$c = new Context;
foreach ($methods as $method) {
@ -113,7 +113,7 @@ class TestContext extends \JKingWeb\Arsse\Test\AbstractTest {
public function testCleanFolderIdArrayValues(): void {
$methods = ["folders", "foldersShallow"];
$in = [1, "2", 3.5, 4.0, 4, "ook", 0, -20, true, false, null, new \DateTime(), -1.0];
$in = [1, "2", 3.5, 4.0, 4, "ook", 0, -20, true, false, null, new \DateTime, -1.0];
$out = [1, 2, 4, 0];
$c = new Context;
foreach ($methods as $method) {
@ -169,7 +169,7 @@ class TestContext extends \JKingWeb\Arsse\Test\AbstractTest {
$this->assertTrue(isset($c1[2]));
$c1[] = $c2;
$act = [];
foreach($c1 as $k => $v) {
foreach ($c1 as $k => $v) {
$act[$k] = $v;
}
$exp = [2 => $c3, $c2];

6
tests/cases/Misc/TestQuery.php

@ -9,9 +9,9 @@ namespace JKingWeb\Arsse\TestCase\Misc;
use JKingWeb\Arsse\Misc\Query;
use JKingWeb\Arsse\Misc\QueryFilter;
/**
/**
* @covers \JKingWeb\Arsse\Misc\Query
* @covers \JKingWeb\Arsse\Misc\QueryFilter
* @covers \JKingWeb\Arsse\Misc\QueryFilter
*/
class TestQuery extends \JKingWeb\Arsse\Test\AbstractTest {
public function testBasicQuery(): void {
@ -112,4 +112,4 @@ class TestQuery extends \JKingWeb\Arsse\Test\AbstractTest {
$this->assertSame(["datetime", "str", "int", "str", "int"], $q->getTypes());
$this->assertSame([1, "ook", 42, "ook", 42], $q->getValues());
}
}
}

1
tests/cases/Misc/TestURL.php

@ -10,7 +10,6 @@ use JKingWeb\Arsse\Misc\URL;
/** @covers \JKingWeb\Arsse\Misc\URL */
class TestURL extends \JKingWeb\Arsse\Test\AbstractTest {
/** @dataProvider provideNormalizations */
public function testNormalizeAUrl(string $url, string $exp, string $user = null, string $pass = null): void {
$this->assertSame($exp, URL::normalize($url, $user, $pass));

2
tests/cases/REST/Miniflux/TestV1.php

@ -93,7 +93,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
Arsse::$user->method("propertiesGet")->willReturn(['num' => 42, 'admin' => false, 'root_folder_name' => null, 'tz' => "Asia/Gaza"]);
Arsse::$user->method("begin")->willReturn($this->transaction->get());
//initialize a handler
$this->h = new V1();
$this->h = new V1;
}
protected function v($value) {

2
tests/cases/REST/NextcloudNews/TestV1_2.php

@ -328,7 +328,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock = $this->mock(Database::class);
$this->dbMock->begin->returns($this->mock(Transaction::class));
//initialize a handler
$this->h = new V1_2();
$this->h = new V1_2;
}
protected function v($value) {

9
tests/cases/REST/TestREST.php

@ -22,7 +22,6 @@ use Laminas\Diactoros\Response\EmptyResponse;
/** @covers \JKingWeb\Arsse\REST */
class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
/** @dataProvider provideApiMatchData */
public function testMatchAUrlToAnApi($apiList, string $input, array $exp): void {
$r = new REST($apiList);
@ -61,7 +60,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
/** @dataProvider provideAuthenticableRequests */
public function testAuthenticateRequests(array $serverParams, array $expAttr): void {
$r = new REST();
$r = new REST;
// create a mock user manager
$this->userMock = $this->mock(User::class);
$this->userMock->auth->returns(false);
@ -95,7 +94,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
public function testSendAuthenticationChallenges(): void {
self::setConf();
$r = new REST();
$r = new REST;
$in = new EmptyResponse(401);
$exp = $in->withHeader("WWW-Authenticate", 'Basic realm="OOK", charset="UTF-8"');
$act = $r->challenge($in, "OOK");
@ -107,7 +106,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
/** @dataProvider provideUnnormalizedOrigins */
public function testNormalizeOrigins(string $origin, string $exp, array $ports = null): void {
$r = new REST();
$r = new REST;
$act = $r->corsNormalizeOrigin($origin, $ports);
$this->assertSame($exp, $act);
}
@ -188,7 +187,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
/** @dataProvider provideCorsHeaders */
public function testAddCorsHeaders(string $reqMethod, array $reqHeaders, array $resHeaders, array $expHeaders): void {
$r = new REST();
$r = new REST;
$req = new Request("", $reqMethod, "php://memory", $reqHeaders);
$res = new EmptyResponse(204, $resHeaders);
$exp = new EmptyResponse(204, $expHeaders);

12
tests/cases/REST/TinyTinyRSS/TestAPI.php

@ -147,7 +147,7 @@ LONG_STRING;
'expires' => "2112-12-21 21:12:00",
'user' => $this->userId,
]);
$this->h = new API();
$this->h = new API;
}
protected function req($data, string $method = "POST", string $target = "", string $strData = null, string $user = null): ResponseInterface {
@ -1524,11 +1524,11 @@ LONG_STRING;
'content' => '<p>Article content 1</p>',
],
[
'id' => "102",
'guid' => "SHA256:5be8a5a46ecd52ed132191c8d27fb1af6b3d4edc00234c5d9f8f0e10562ed3b7",
'title' => 'Article title 2',
'link' => 'http://example.com/2',
'labels' => [],
'id' => "102",
'guid' => "SHA256:5be8a5a46ecd52ed132191c8d27fb1af6b3d4edc00234c5d9f8f0e10562ed3b7",
'title' => 'Article title 2',
'link' => 'http://example.com/2',
'labels' => [],
'unread' => false,
'marked' => false,
'published' => false,

2
tests/cases/REST/TinyTinyRSS/TestIcon.php

@ -25,7 +25,7 @@ class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest {
Arsse::$user = $this->mock(User::class)->get();
// create a mock database interface
$this->dbMock = $this->mock(Database::class);
$this->h = new Icon();
$this->h = new Icon;
}
protected function req(string $target, string $method = "GET", string $user = null): ResponseInterface {

2
tests/cases/Service/TestService.php

@ -20,7 +20,7 @@ class TestService extends \JKingWeb\Arsse\Test\AbstractTest {
self::setConf();
$this->dbMock = $this->mock(Database::class);
Arsse::$db = $this->dbMock->get();
$this->srv = new Service();
$this->srv = new Service;
}
public function testCheckIn(): void {

323
tests/lib/AbstractTest.php

@ -31,6 +31,152 @@ use Laminas\Diactoros\Response\XmlResponse;
abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
use \DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
protected const COL_DEFS = [
'arsse_meta' => [
'key' => "str",
'value' => "str",
],
'arsse_users' => [
'id' => "str",
'password' => "str",
'num' => "int",
'admin' => "bool",
],
'arsse_user_meta' => [
'owner' => "str",
'key' => "str",
'modified' => "datetime",
'value' => "str",
],
'arsse_sessions' => [
'id' => "str",
'created' => "datetime",
'expires' => "datetime",
'user' => "str",
],
'arsse_tokens' => [
'id' => "str",
'class' => "str",
'user' => "str",
'created' => "datetime",
'expires' => "datetime",
'data' => "str",
],
'arsse_feeds' => [
'id' => "int",
'url' => "str",
'title' => "str",
'source' => "str",
'updated' => "datetime",
'modified' => "datetime",
'next_fetch' => "datetime",
'orphaned' => "datetime",
'etag' => "str",
'err_count' => "int",
'err_msg' => "str",
'username' => "str",
'password' => "str",
'size' => "int",
'icon' => "int",
],
'arsse_icons' => [
'id' => "int",
'url' => "str",
'modified' => "datetime",
'etag' => "str",
'next_fetch' => "datetime",
'orphaned' => "datetime",
'type' => "str",
'data' => "blob",
],
'arsse_articles' => [
'id' => "int",
'feed' => "int",
'url' => "str",
'title' => "str",
'author' => "str",
'published' => "datetime",
'edited' => "datetime",
'modified' => "datetime",
'guid' => "str",
'url_title_hash' => "str",
'url_content_hash' => "str",
'title_content_hash' => "str",
'content_scraped' => "str",
'content' => "str",
],
'arsse_editions' => [
'id' => "int",
'article' => "int",
'modified' => "datetime",
],
'arsse_enclosures' => [
'article' => "int",
'url' => "str",
'type' => "str",
],
'arsse_categories' => [
'article' => "int",
'name' => "str",
],
'arsse_marks' => [
'article' => "int",
'subscription' => "int",
'read' => "bool",
'starred' => "bool",
'modified' => "datetime",
'note' => "str",
'touched' => "bool",
'hidden' => "bool",
],
'arsse_subscriptions' => [
'id' => "int",
'owner' => "str",
'feed' => "int",
'added' => "datetime",
'modified' => "datetime",
'title' => "str",
'order_type' => "int",
'pinned' => "bool",
'folder' => "int",
'keep_rule' => "str",
'block_rule' => "str",
'scrape' => "bool",
],
'arsse_folders' => [
'id' => "int",
'owner' => "str",
'parent' => "int",
'name' => "str",
'modified' => "datetime",
],
'arsse_tags' => [
'id' => "int",
'owner' => "str",
'name' => "str",
'modified' => "datetime",
],
'arsse_tag_members' => [
'tag' => "int",
'subscription' => "int",
'assigned' => "bool",
'modified' => "datetime",
],
'arsse_labels' => [
'id' => "int",
'owner' => "str",
'name' => "str",
'modified' => "datetime",
],
'arsse_label_members' => [
'label' => "int",
'article' => "int",
'subscription' => "int",
'assigned' => "bool",
'modified' => "datetime",
],
];
protected $objMock;
protected $confMock;
protected $langMock;
@ -54,7 +200,7 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
Arsse::$$prop = null;
}
if ($loadLang) {
Arsse::$lang = new \JKingWeb\Arsse\Lang();
Arsse::$lang = new \JKingWeb\Arsse\Lang;
}
}
@ -62,17 +208,17 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
$defaults = [
'dbSQLite3File' => ":memory:",
'dbSQLite3Timeout' => 0,
'dbPostgreSQLHost' => $_ENV['ARSSE_TEST_PGSQL_HOST'] ?: "",
'dbPostgreSQLPort' => $_ENV['ARSSE_TEST_PGSQL_PORT'] ?: 5432,
'dbPostgreSQLUser' => $_ENV['ARSSE_TEST_PGSQL_USER'] ?: "arsse_test",
'dbPostgreSQLPass' => $_ENV['ARSSE_TEST_PGSQL_PASS'] ?: "arsse_test",
'dbPostgreSQLDb' => $_ENV['ARSSE_TEST_PGSQL_DB'] ?: "arsse_test",
'dbPostgreSQLHost' => $_ENV['ARSSE_TEST_PGSQL_HOST'] ?: "",
'dbPostgreSQLPort' => $_ENV['ARSSE_TEST_PGSQL_PORT'] ?: 5432,
'dbPostgreSQLUser' => $_ENV['ARSSE_TEST_PGSQL_USER'] ?: "arsse_test",
'dbPostgreSQLPass' => $_ENV['ARSSE_TEST_PGSQL_PASS'] ?: "arsse_test",
'dbPostgreSQLDb' => $_ENV['ARSSE_TEST_PGSQL_DB'] ?: "arsse_test",
'dbPostgreSQLSchema' => $_ENV['ARSSE_TEST_PGSQL_SCHEMA'] ?: "arsse_test",
'dbMySQLHost' => $_ENV['ARSSE_TEST_MYSQL_HOST'] ?: "localhost",
'dbMySQLPort' => $_ENV['ARSSE_TEST_MYSQL_PORT'] ?: 3306,
'dbMySQLUser' => $_ENV['ARSSE_TEST_MYSQL_USER'] ?: "arsse_test",
'dbMySQLPass' => $_ENV['ARSSE_TEST_MYSQL_PASS'] ?: "arsse_test",
'dbMySQLDb' => $_ENV['ARSSE_TEST_MYSQL_DB'] ?: "arsse_test",
'dbMySQLHost' => $_ENV['ARSSE_TEST_MYSQL_HOST'] ?: "localhost",
'dbMySQLPort' => $_ENV['ARSSE_TEST_MYSQL_PORT'] ?: 3306,
'dbMySQLUser' => $_ENV['ARSSE_TEST_MYSQL_USER'] ?: "arsse_test",
'dbMySQLPass' => $_ENV['ARSSE_TEST_MYSQL_PASS'] ?: "arsse_test",
'dbMySQLDb' => $_ENV['ARSSE_TEST_MYSQL_DB'] ?: "arsse_test",
];
Arsse::$conf = (($force ? null : Arsse::$conf) ?? (new Conf))->import($defaults)->import($conf);
}
@ -241,14 +387,33 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
return $value;
}
/** Inserts into the database test data in the following format:
*
* ```php
* $data = [
* 'some_table' => [
* 'columns' => ["id", "name"],
* 'rows' => [
* [1,"Dupond"],
* [2,"Dupont"],
* ]
* ],
* 'other_table' => [
* ...
* ]
* ];
* ```
*/
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']));
}, $info['columns']);
$cols = implode(",", $cols);
$bindings = array_values($info['columns']);
$bindings = array_map(function($c) use ($table) {
return self::COL_DEFS[$table][$c];
}, $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) {
@ -260,70 +425,104 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
return true;
}
public function compareExpectations(Driver $drv, array $expected): bool {
public function compareExpectations(Driver $drv, array $expected): void {
foreach ($expected as $table => $info) {
$cols = array_map(function($v) {
// serialize the rows of the expected output
$exp = [];
$dates = [];
foreach ($info['rows'] as $r) {
$row = [];
foreach ($r as $c => $v) {
// store any date values for later comparison
if (self::COL_DEFS[$table][$info['columns'][$c]] === "datetime") {
$dates[] = $v;
}
// serialize to CSV, null being represented by no value
if ($v === null) {
$row[] = "";
} elseif ($drv->stringOutput() || is_string($v)) {
$row[] = '"'.str_replace('"', '""', (string) $v).'"';
} else {
$row[] = (string) $v;
}
}
$exp[] = implode(",", $row);
}
// serialize the rows of the actual output
$cols = implode(",", array_map(function($v) {
return '"'.str_replace('"', '""', $v).'"';
}, array_keys($info['columns']));
$cols = implode(",", $cols);
$types = $info['columns'];
}, $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 columns in array index $index of expectations for table $table does not match its definition");
$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;
$act = [];
$extra = [];
foreach ($data as $r) {
$row = [];
foreach ($r as $c => $v) {
// account for dates which might be off by one second
if (self::COL_DEFS[$table][$c] === "datetime") {
if (array_search($v, $dates, true) === false) {
$v = Date::transform(Date::sub("PT1S", $v), "sql");
if (array_search($v, $dates, true) === false) {
$v = Date::transform(Date::add("PT2S", $v), "sql");
if (array_search($v, $dates, true) === false) {
$v = Date::transform(Date::sub("PT1S", $v), "sql");
}
}
}
}
if ($row === $test) {
$data[$index] = $test;
break;
if ($v === null) {
$row[] = "";
} elseif (is_string($v)) {
$row[] = '"'.str_replace('"', '""', (string) $v).'"';
} else {
$row[] = (string) $v;
}
}
$this->assertContains($row, $data, "Actual Table $table does not contain record at expected array index $index");
$found = array_search($row, $data, true);
unset($data[$found]);
$row = implode(",", $row);
// now search for the actual output row in the expected output
$found = array_keys($exp, $row, true);
foreach ($found as $k) {
if(!isset($act[$k])) {
$act[$k] = $row;
// skip to the next row
continue 2;
}
}
// if the row was not found, add it to a buffer which will be added to the actual output once all found rows are processed
$extra[] = $row;
}
$this->assertSame([], $data, "Actual table $table contains extra rows not in expectations");
// add any unfound rows to the end of the actual array
$base = sizeof($exp) + 1;
foreach ($extra as $k => $v) {
$act[$base + $k] = $v;
}
// sort the actual output by keys
ksort($act);
// finally perform the comparison to be shown to the tester
$this->assertSame($exp, $act, "Actual table $table does not match expectations");
}
return true;
}
public function primeExpectations(array $source, array $tableSpecs): 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.");
if (!isset($source[$table])) {
throw new Exception("Source for expectations does not contain requested table $table.");
}
// fill the output, particularly the correct number of (empty) rows
$rows = sizeof($source[$table]['rows']);
$out[$table] = [
'columns' => [],
'rows' => array_fill(0, sizeof($source[$table]['rows']), []),
'columns' => $columns,
'rows' => array_fill(0, $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]];
// fill the rows with the requested data, column-wise
foreach ($columns as $c) {
if (($index = array_search($c, $source[$table]['columns'], true)) === false) {
throw new exception("Expected column $table.$c is not present in test data");
}
for ($a = 0; $a < $rows; $a++) {
$out[$table]['rows'][$a][] = $source[$table]['rows'][$a][$index];
}
}
}
@ -335,10 +534,6 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
// stringify our expectations if necessary
if (static::$stringOutput ?? false) {
$expected = $this->stringify($expected);
// MySQL is extra-special and mixes strings and integers, so we cast the data, too
if ((static::$implementation ?? "") === "MySQL") {
$data = $this->stringify($data);
}
}
$this->assertCount(sizeof($expected), $data, "Number of result rows (".sizeof($data).") differs from number of expected rows (".sizeof($expected).")");
if (sizeof($expected)) {

4
tests/lib/DatabaseDrivers/MySQL.php

@ -16,7 +16,7 @@ trait MySQL {
protected static $dbResultClass = \JKingWeb\Arsse\Db\MySQL\Result::class;
protected static $dbStatementClass = \JKingWeb\Arsse\Db\MySQL\Statement::class;
protected static $dbDriverClass = \JKingWeb\Arsse\Db\MySQL\Driver::class;
protected static $stringOutput = true;
protected static $stringOutput = false;
public static function dbInterface() {
if (!class_exists("mysqli")) {
@ -25,7 +25,7 @@ trait MySQL {
$drv = new \mysqli_driver;
$drv->report_mode = \MYSQLI_REPORT_OFF;
$d = mysqli_init();
$d->options(\MYSQLI_OPT_INT_AND_FLOAT_NATIVE, false);
$d->options(\MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
$d->options(\MYSQLI_SET_CHARSET_NAME, "utf8mb4");
@$d->real_connect(Arsse::$conf->dbMySQLHost, Arsse::$conf->dbMySQLUser, Arsse::$conf->dbMySQLPass, Arsse::$conf->dbMySQLDb, Arsse::$conf->dbMySQLPort);
if ($d->connect_errno) {

4
tests/lib/DatabaseDrivers/MySQLPDO.php

@ -16,7 +16,7 @@ trait MySQLPDO {
protected static $dbResultClass = \JKingWeb\Arsse\Db\PDOResult::class;
protected static $dbStatementClass = \JKingWeb\Arsse\Db\MySQL\PDOStatement::class;
protected static $dbDriverClass = \JKingWeb\Arsse\Db\MySQL\PDODriver::class;
protected static $stringOutput = true;
protected static $stringOutput = false;
public static function dbInterface() {
try {
@ -33,7 +33,7 @@ trait MySQLPDO {
$dsn = "mysql:".implode(";", $dsn);
$d = new \PDO($dsn, Arsse::$conf->dbMySQLUser, Arsse::$conf->dbMySQLPass, [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_STRINGIFY_FETCHES => true,
\PDO::ATTR_STRINGIFY_FETCHES => false,
\PDO::MYSQL_ATTR_MULTI_STATEMENTS => false,
]);
foreach (\JKingWeb\Arsse\Db\MySQL\PDODriver::makeSetupQueries() as $q) {

2
vendor-bin/csfixer/composer.json

@ -1,5 +1,5 @@
{
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.8"
"friendsofphp/php-cs-fixer": "^3.0"
}
}

574
vendor-bin/csfixer/composer.lock

File diff suppressed because it is too large

113
vendor-bin/daux/composer.lock

@ -83,16 +83,16 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "7.4.2",
"version": "7.4.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4"
"reference": "74a8602c6faec9ef74b7a9391ac82c5e65b1cdab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/ac1ec1cd9b5624694c3a40be801d94137afb12b4",
"reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/74a8602c6faec9ef74b7a9391ac82c5e65b1cdab",
"reference": "74a8602c6faec9ef74b7a9391ac82c5e65b1cdab",
"shasum": ""
},
"require": {
@ -187,7 +187,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.4.2"
"source": "https://github.com/guzzle/guzzle/tree/7.4.3"
},
"funding": [
{
@ -203,7 +203,7 @@
"type": "tidelift"
}
],
"time": "2022-03-20T14:16:28+00:00"
"time": "2022-05-25T13:24:33+00:00"
},
{
"name": "guzzlehttp/promises",
@ -898,16 +898,16 @@
},
{
"name": "symfony/console",
"version": "v5.4.7",
"version": "v5.4.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "900275254f0a1a2afff1ab0e11abd5587a10e1d6"
"reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/900275254f0a1a2afff1ab0e11abd5587a10e1d6",
"reference": "900275254f0a1a2afff1ab0e11abd5587a10e1d6",
"url": "https://api.github.com/repos/symfony/console/zipball/829d5d1bf60b2efeb0887b7436873becc71a45eb",
"reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb",
"shasum": ""
},
"require": {
@ -977,7 +977,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v5.4.7"
"source": "https://github.com/symfony/console/tree/v5.4.9"
},
"funding": [
{
@ -993,29 +993,29 @@
"type": "tidelift"
}
],
"time": "2022-03-31T17:09:19+00:00"
"time": "2022-05-18T06:17:34+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.0.0",
"version": "v3.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced"
"reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/c726b64c1ccfe2896cb7df2e1331c357ad1c8ced",
"reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918",
"reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918",
"shasum": ""
},
"require": {
"php": ">=8.0.2"
"php": ">=8.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.0-dev"
"dev-main": "3.1-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -1044,7 +1044,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.0"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.0"
},
"funding": [
{
@ -1060,20 +1060,20 @@
"type": "tidelift"
}
],
"time": "2021-11-01T23:48:49+00:00"
"time": "2022-02-25T11:15:52+00:00"
},
{
"name": "symfony/http-foundation",
"version": "v5.4.6",
"version": "v5.4.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
"reference": "34e89bc147633c0f9dd6caaaf56da3b806a21465"
"reference": "6b0d0e4aca38d57605dcd11e2416994b38774522"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/34e89bc147633c0f9dd6caaaf56da3b806a21465",
"reference": "34e89bc147633c0f9dd6caaaf56da3b806a21465",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/6b0d0e4aca38d57605dcd11e2416994b38774522",
"reference": "6b0d0e4aca38d57605dcd11e2416994b38774522",
"shasum": ""
},
"require": {
@ -1117,7 +1117,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-foundation/tree/v5.4.6"
"source": "https://github.com/symfony/http-foundation/tree/v5.4.9"
},
"funding": [
{
@ -1133,20 +1133,20 @@
"type": "tidelift"
}
],
"time": "2022-03-05T21:03:43+00:00"
"time": "2022-05-17T15:07:29+00:00"
},
{
"name": "symfony/mime",
"version": "v5.4.7",
"version": "v5.4.9",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
"reference": "92d27a34dea2e199fa9b687e3fff3a7d169b7b1c"
"reference": "2b3802a24e48d0cfccf885173d2aac91e73df92e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/mime/zipball/92d27a34dea2e199fa9b687e3fff3a7d169b7b1c",
"reference": "92d27a34dea2e199fa9b687e3fff3a7d169b7b1c",
"url": "https://api.github.com/repos/symfony/mime/zipball/2b3802a24e48d0cfccf885173d2aac91e73df92e",
"reference": "2b3802a24e48d0cfccf885173d2aac91e73df92e",
"shasum": ""
},
"require": {
@ -1200,7 +1200,7 @@
"mime-type"
],
"support": {
"source": "https://github.com/symfony/mime/tree/v5.4.7"
"source": "https://github.com/symfony/mime/tree/v5.4.9"
},
"funding": [
{
@ -1216,7 +1216,7 @@
"type": "tidelift"
}
],
"time": "2022-03-11T16:08:05+00:00"
"time": "2022-05-21T10:24:18+00:00"
},
{
"name": "symfony/polyfill-ctype",
@ -1962,16 +1962,16 @@
},
{
"name": "symfony/process",
"version": "v5.4.7",
"version": "v5.4.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "38a44b2517b470a436e1c944bf9b9ba3961137fb"
"reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/38a44b2517b470a436e1c944bf9b9ba3961137fb",
"reference": "38a44b2517b470a436e1c944bf9b9ba3961137fb",
"url": "https://api.github.com/repos/symfony/process/zipball/597f3fff8e3e91836bb0bd38f5718b56ddbde2f3",
"reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3",
"shasum": ""
},
"require": {
@ -2004,7 +2004,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v5.4.7"
"source": "https://github.com/symfony/process/tree/v5.4.8"
},
"funding": [
{
@ -2020,24 +2020,24 @@
"type": "tidelift"
}
],
"time": "2022-03-18T16:18:52+00:00"
"time": "2022-04-08T05:07:18+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v3.0.0",
"version": "v3.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "36715ebf9fb9db73db0cb24263c79077c6fe8603"
"reference": "d66cd8ab656780f62c4215b903a420eb86358957"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/36715ebf9fb9db73db0cb24263c79077c6fe8603",
"reference": "36715ebf9fb9db73db0cb24263c79077c6fe8603",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/d66cd8ab656780f62c4215b903a420eb86358957",
"reference": "d66cd8ab656780f62c4215b903a420eb86358957",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"php": ">=8.1",
"psr/container": "^2.0"
},
"conflict": {
@ -2049,7 +2049,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.0-dev"
"dev-main": "3.1-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -2059,7 +2059,10 @@
"autoload": {
"psr-4": {
"Symfony\\Contracts\\Service\\": ""
}
},
"exclude-from-classmap": [
"/Test/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -2086,7 +2089,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.0.0"
"source": "https://github.com/symfony/service-contracts/tree/v3.1.0"
},
"funding": [
{
@ -2102,24 +2105,24 @@
"type": "tidelift"
}
],
"time": "2021-11-04T17:53:12+00:00"
"time": "2022-05-07T08:07:09+00:00"
},
{
"name": "symfony/string",
"version": "v6.0.3",
"version": "v6.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2"
"reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/522144f0c4c004c80d56fa47e40e17028e2eefc2",
"reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2",
"url": "https://api.github.com/repos/symfony/string/zipball/d3edc75baf9f1d4f94879764dda2e1ac33499529",
"reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"php": ">=8.1",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-intl-grapheme": "~1.0",
"symfony/polyfill-intl-normalizer": "~1.0",
@ -2171,7 +2174,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v6.0.3"
"source": "https://github.com/symfony/string/tree/v6.1.0"
},
"funding": [
{
@ -2187,7 +2190,7 @@
"type": "tidelift"
}
],
"time": "2022-01-02T09:55:41+00:00"
"time": "2022-04-22T08:18:23+00:00"
},
{
"name": "symfony/yaml",
@ -2344,5 +2347,5 @@
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.2.0"
"plugin-api-version": "2.3.0"
}

2
vendor-bin/phpstan/composer.lock

@ -79,5 +79,5 @@
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.2.0"
"plugin-api-version": "2.3.0"
}

80
vendor-bin/phpunit/composer.lock

@ -448,16 +448,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.13.2",
"version": "v4.14.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "210577fe3cf7badcc5814d99455df46564f3c077"
"reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077",
"reference": "210577fe3cf7badcc5814d99455df46564f3c077",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1",
"reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1",
"shasum": ""
},
"require": {
@ -498,9 +498,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0"
},
"time": "2021-11-30T19:35:32+00:00"
"time": "2022-05-31T20:59:12+00:00"
},
{
"name": "phar-io/manifest",
@ -2417,21 +2417,20 @@
},
{
"name": "webmozart/glob",
"version": "4.4.0",
"version": "4.6.0",
"source": {
"type": "git",
"url": "https://github.com/webmozarts/glob.git",
"reference": "539b5dbc10021d3f9242e7a9e9b6b37843179e83"
"reference": "3c17f7dec3d9d0e87b575026011f2e75a56ed655"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozarts/glob/zipball/539b5dbc10021d3f9242e7a9e9b6b37843179e83",
"reference": "539b5dbc10021d3f9242e7a9e9b6b37843179e83",
"url": "https://api.github.com/repos/webmozarts/glob/zipball/3c17f7dec3d9d0e87b575026011f2e75a56ed655",
"reference": "3c17f7dec3d9d0e87b575026011f2e75a56ed655",
"shasum": ""
},
"require": {
"php": "^7.3 || ^8.0.0",
"webmozart/path-util": "^2.2"
"php": "^7.3 || ^8.0.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
@ -2461,60 +2460,9 @@
"description": "A PHP implementation of Ant's glob.",
"support": {
"issues": "https://github.com/webmozarts/glob/issues",
"source": "https://github.com/webmozarts/glob/tree/4.4.0"
"source": "https://github.com/webmozarts/glob/tree/4.6.0"
},
"time": "2021-10-07T16:13:08+00:00"
},
{
"name": "webmozart/path-util",
"version": "2.3.0",
"source": {
"type": "git",
"url": "https://github.com/webmozart/path-util.git",
"reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725",
"reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"webmozart/assert": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "^4.6",
"sebastian/version": "^1.0.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
}
},
"autoload": {
"psr-4": {
"Webmozart\\PathUtil\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
}
],
"description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.",
"support": {
"issues": "https://github.com/webmozart/path-util/issues",
"source": "https://github.com/webmozart/path-util/tree/2.3.0"
},
"abandoned": "symfony/filesystem",
"time": "2015-12-17T08:42:14+00:00"
"time": "2022-05-24T19:45:58+00:00"
}
],
"aliases": [],
@ -2524,5 +2472,5 @@
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.2.0"
"plugin-api-version": "2.3.0"
}

241
vendor-bin/robo/composer.lock

@ -90,22 +90,22 @@
},
{
"name": "consolidation/annotated-command",
"version": "4.5.3",
"version": "4.5.5",
"source": {
"type": "git",
"url": "https://github.com/consolidation/annotated-command.git",
"reference": "1941a743e63993288e09d0686a4cb7ed47813213"
"reference": "67cea8e8e7656b74da651ea6f49321853996c0fd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/consolidation/annotated-command/zipball/1941a743e63993288e09d0686a4cb7ed47813213",
"reference": "1941a743e63993288e09d0686a4cb7ed47813213",
"url": "https://api.github.com/repos/consolidation/annotated-command/zipball/67cea8e8e7656b74da651ea6f49321853996c0fd",
"reference": "67cea8e8e7656b74da651ea6f49321853996c0fd",
"shasum": ""
},
"require": {
"consolidation/output-formatters": "^4.1.1",
"php": ">=7.1.3",
"psr/log": "^1|^2",
"psr/log": "^1|^2|^3",
"symfony/console": "^4.4.8|^5|^6",
"symfony/event-dispatcher": "^4.4.8|^5|^6",
"symfony/finder": "^4.4.8|^5|^6"
@ -119,7 +119,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "5.x-dev"
"dev-main": "4.x-dev"
}
},
"autoload": {
@ -140,9 +140,9 @@
"description": "Initialize Symfony Console commands from annotated command class methods.",
"support": {
"issues": "https://github.com/consolidation/annotated-command/issues",
"source": "https://github.com/consolidation/annotated-command/tree/4.5.3"
"source": "https://github.com/consolidation/annotated-command/tree/4.5.5"
},
"time": "2022-04-02T00:17:53+00:00"
"time": "2022-04-26T16:18:25+00:00"
},
{
"name": "consolidation/config",
@ -546,26 +546,25 @@
},
{
"name": "grasmash/expander",
"version": "2.0.2",
"version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/grasmash/expander.git",
"reference": "f4df21d01d1fbda38269cca89e3dbb6ba223da7f"
"reference": "b7cbc1f2fdf9a9c0e253a424c2a4058316b7cb6e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/grasmash/expander/zipball/f4df21d01d1fbda38269cca89e3dbb6ba223da7f",
"reference": "f4df21d01d1fbda38269cca89e3dbb6ba223da7f",
"url": "https://api.github.com/repos/grasmash/expander/zipball/b7cbc1f2fdf9a9c0e253a424c2a4058316b7cb6e",
"reference": "b7cbc1f2fdf9a9c0e253a424c2a4058316b7cb6e",
"shasum": ""
},
"require": {
"dflydev/dot-access-data": "^3.0.0",
"php": ">=5.6",
"psr/log": "^1 | ^2"
"php": ">=7.1",
"psr/log": "^1 | ^2 | ^3"
},
"require-dev": {
"greg-1-anderson/composer-test-scenarios": "^1",
"php-coveralls/php-coveralls": "^2.0",
"phpunit/phpunit": "^6.0 || ^8.0 || ^9",
"squizlabs/php_codesniffer": "^2.7 || ^3.3"
},
@ -592,9 +591,9 @@
"description": "Expands internal property references in PHP arrays file.",
"support": {
"issues": "https://github.com/grasmash/expander/issues",
"source": "https://github.com/grasmash/expander/tree/2.0.2"
"source": "https://github.com/grasmash/expander/tree/2.0.3"
},
"time": "2022-02-24T03:58:20+00:00"
"time": "2022-04-25T22:17:46+00:00"
},
{
"name": "league/container",
@ -1071,20 +1070,21 @@
},
{
"name": "symfony/console",
"version": "v6.0.7",
"version": "v6.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "70dcf7b2ca2ea08ad6ebcc475f104a024fb5632e"
"reference": "c9646197ef43b0e2ff44af61e7f0571526fd4170"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/70dcf7b2ca2ea08ad6ebcc475f104a024fb5632e",
"reference": "70dcf7b2ca2ea08ad6ebcc475f104a024fb5632e",
"url": "https://api.github.com/repos/symfony/console/zipball/c9646197ef43b0e2ff44af61e7f0571526fd4170",
"reference": "c9646197ef43b0e2ff44af61e7f0571526fd4170",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"php": ">=8.1",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/polyfill-mbstring": "~1.0",
"symfony/service-contracts": "^1.1|^2|^3",
"symfony/string": "^5.4|^6.0"
@ -1146,7 +1146,74 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v6.0.7"
"source": "https://github.com/symfony/console/tree/v6.1.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-05-27T06:34:22+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918",
"reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.1-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.0"
},
"funding": [
{
@ -1162,24 +1229,24 @@
"type": "tidelift"
}
],
"time": "2022-03-31T17:18:25+00:00"
"time": "2022-02-25T11:15:52+00:00"
},
{
"name": "symfony/event-dispatcher",
"version": "v6.0.3",
"version": "v6.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "6472ea2dd415e925b90ca82be64b8bc6157f3934"
"reference": "a0449a7ad7daa0f7c0acd508259f80544ab5a347"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/6472ea2dd415e925b90ca82be64b8bc6157f3934",
"reference": "6472ea2dd415e925b90ca82be64b8bc6157f3934",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a0449a7ad7daa0f7c0acd508259f80544ab5a347",
"reference": "a0449a7ad7daa0f7c0acd508259f80544ab5a347",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"php": ">=8.1",
"symfony/event-dispatcher-contracts": "^2|^3"
},
"conflict": {
@ -1229,7 +1296,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v6.0.3"
"source": "https://github.com/symfony/event-dispatcher/tree/v6.1.0"
},
"funding": [
{
@ -1245,24 +1312,24 @@
"type": "tidelift"
}
],
"time": "2022-01-02T09:55:41+00:00"
"time": "2022-05-05T16:51:07+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
"version": "v3.0.0",
"version": "v3.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
"reference": "aa5422287b75594b90ee9cd807caf8f0df491385"
"reference": "02ff5eea2f453731cfbc6bc215e456b781480448"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/aa5422287b75594b90ee9cd807caf8f0df491385",
"reference": "aa5422287b75594b90ee9cd807caf8f0df491385",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/02ff5eea2f453731cfbc6bc215e456b781480448",
"reference": "02ff5eea2f453731cfbc6bc215e456b781480448",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"php": ">=8.1",
"psr/event-dispatcher": "^1"
},
"suggest": {
@ -1271,7 +1338,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.0-dev"
"dev-main": "3.1-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -1308,7 +1375,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.0.0"
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.1.0"
},
"funding": [
{
@ -1324,24 +1391,24 @@
"type": "tidelift"
}
],
"time": "2021-07-15T12:33:35+00:00"
"time": "2022-02-25T11:15:52+00:00"
},
{
"name": "symfony/filesystem",
"version": "v6.0.7",
"version": "v6.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "6c9e4c41f2c51dfde3db298594ed9cba55dbf5ff"
"reference": "3132d2f43ca799c2aa099f9738d98228c56baa5d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/6c9e4c41f2c51dfde3db298594ed9cba55dbf5ff",
"reference": "6c9e4c41f2c51dfde3db298594ed9cba55dbf5ff",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/3132d2f43ca799c2aa099f9738d98228c56baa5d",
"reference": "3132d2f43ca799c2aa099f9738d98228c56baa5d",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"php": ">=8.1",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.8"
},
@ -1371,7 +1438,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v6.0.7"
"source": "https://github.com/symfony/filesystem/tree/v6.1.0"
},
"funding": [
{
@ -1387,24 +1454,27 @@
"type": "tidelift"
}
],
"time": "2022-04-01T12:54:51+00:00"
"time": "2022-05-21T13:34:40+00:00"
},
{
"name": "symfony/finder",
"version": "v6.0.3",
"version": "v6.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "8661b74dbabc23223f38c9b99d3f8ade71170430"
"reference": "45b8beb69d6eb3b05a65689ebfd4222326773f8f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/8661b74dbabc23223f38c9b99d3f8ade71170430",
"reference": "8661b74dbabc23223f38c9b99d3f8ade71170430",
"url": "https://api.github.com/repos/symfony/finder/zipball/45b8beb69d6eb3b05a65689ebfd4222326773f8f",
"reference": "45b8beb69d6eb3b05a65689ebfd4222326773f8f",
"shasum": ""
},
"require": {
"php": ">=8.0.2"
"php": ">=8.1"
},
"require-dev": {
"symfony/filesystem": "^6.0"
},
"type": "library",
"autoload": {
@ -1432,7 +1502,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v6.0.3"
"source": "https://github.com/symfony/finder/tree/v6.1.0"
},
"funding": [
{
@ -1448,7 +1518,7 @@
"type": "tidelift"
}
],
"time": "2022-01-26T17:23:29+00:00"
"time": "2022-04-15T08:08:08+00:00"
},
{
"name": "symfony/polyfill-ctype",
@ -1782,20 +1852,20 @@
},
{
"name": "symfony/process",
"version": "v6.0.7",
"version": "v6.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "e13f6757e267d687e20ec5b26ccfcbbe511cd8f4"
"reference": "318718453c2be58266f1a9e74063d13cb8dd4165"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/e13f6757e267d687e20ec5b26ccfcbbe511cd8f4",
"reference": "e13f6757e267d687e20ec5b26ccfcbbe511cd8f4",
"url": "https://api.github.com/repos/symfony/process/zipball/318718453c2be58266f1a9e74063d13cb8dd4165",
"reference": "318718453c2be58266f1a9e74063d13cb8dd4165",
"shasum": ""
},
"require": {
"php": ">=8.0.2"
"php": ">=8.1"
},
"type": "library",
"autoload": {
@ -1823,7 +1893,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v6.0.7"
"source": "https://github.com/symfony/process/tree/v6.1.0"
},
"funding": [
{
@ -1839,24 +1909,24 @@
"type": "tidelift"
}
],
"time": "2022-03-18T16:21:55+00:00"
"time": "2022-05-11T12:12:29+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v3.0.0",
"version": "v3.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "36715ebf9fb9db73db0cb24263c79077c6fe8603"
"reference": "d66cd8ab656780f62c4215b903a420eb86358957"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/36715ebf9fb9db73db0cb24263c79077c6fe8603",
"reference": "36715ebf9fb9db73db0cb24263c79077c6fe8603",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/d66cd8ab656780f62c4215b903a420eb86358957",
"reference": "d66cd8ab656780f62c4215b903a420eb86358957",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"php": ">=8.1",
"psr/container": "^2.0"
},
"conflict": {
@ -1868,7 +1938,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.0-dev"
"dev-main": "3.1-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -1878,7 +1948,10 @@
"autoload": {
"psr-4": {
"Symfony\\Contracts\\Service\\": ""
}
},
"exclude-from-classmap": [
"/Test/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -1905,7 +1978,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.0.0"
"source": "https://github.com/symfony/service-contracts/tree/v3.1.0"
},
"funding": [
{
@ -1921,24 +1994,24 @@
"type": "tidelift"
}
],
"time": "2021-11-04T17:53:12+00:00"
"time": "2022-05-07T08:07:09+00:00"
},
{
"name": "symfony/string",
"version": "v6.0.3",
"version": "v6.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2"
"reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/522144f0c4c004c80d56fa47e40e17028e2eefc2",
"reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2",
"url": "https://api.github.com/repos/symfony/string/zipball/d3edc75baf9f1d4f94879764dda2e1ac33499529",
"reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"php": ">=8.1",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-intl-grapheme": "~1.0",
"symfony/polyfill-intl-normalizer": "~1.0",
@ -1990,7 +2063,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v6.0.3"
"source": "https://github.com/symfony/string/tree/v6.1.0"
},
"funding": [
{
@ -2006,24 +2079,24 @@
"type": "tidelift"
}
],
"time": "2022-01-02T09:55:41+00:00"
"time": "2022-04-22T08:18:23+00:00"
},
{
"name": "symfony/yaml",
"version": "v6.0.3",
"version": "v6.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "e77f3ea0b21141d771d4a5655faa54f692b34af5"
"reference": "84ce4f9d2d68f306f971a39d949d8f4b5550dba2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/e77f3ea0b21141d771d4a5655faa54f692b34af5",
"reference": "e77f3ea0b21141d771d4a5655faa54f692b34af5",
"url": "https://api.github.com/repos/symfony/yaml/zipball/84ce4f9d2d68f306f971a39d949d8f4b5550dba2",
"reference": "84ce4f9d2d68f306f971a39d949d8f4b5550dba2",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"php": ">=8.1",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
@ -2064,7 +2137,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v6.0.3"
"source": "https://github.com/symfony/yaml/tree/v6.1.0"
},
"funding": [
{
@ -2080,7 +2153,7 @@
"type": "tidelift"
}
],
"time": "2022-01-26T17:23:29+00:00"
"time": "2022-04-15T14:25:02+00:00"
}
],
"aliases": [],
@ -2090,5 +2163,5 @@
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.2.0"
"plugin-api-version": "2.3.0"
}

Loading…
Cancel
Save