Compare commits

...

10 Commits

  1. 1
      .gitignore
  2. 30
      .php-cs-fixer.dist.php
  3. 2
      RoboFile.php
  4. 2
      UPGRADING
  5. 12
      composer.lock
  6. 1
      lib/Context/ExclusionMembers.php
  7. 50
      lib/Database.php
  8. 4
      lib/Db/AbstractDriver.php
  9. 4
      lib/Db/Driver.php
  10. 6
      lib/Db/MySQL/Driver.php
  11. 4
      lib/Db/MySQL/ExceptionBuilder.php
  12. 2
      lib/Db/MySQL/PDODriver.php
  13. 4
      lib/Db/PostgreSQL/Driver.php
  14. 4
      lib/Db/PostgreSQL/PDODriver.php
  15. 1
      lib/Db/PostgreSQL/PDOResult.php
  16. 4
      lib/Db/SQLite3/PDODriver.php
  17. 1
      lib/Misc/URL.php
  18. 16
      lib/Misc/ValueInfo.php
  19. 4
      lib/REST.php
  20. 2
      lib/REST/Miniflux/V1.php
  21. 8
      lib/REST/NextcloudNews/V1_2.php
  22. 2
      lib/REST/TinyTinyRSS/API.php
  23. 2
      lib/REST/TinyTinyRSS/Search.php
  24. 6
      lib/Service.php
  25. 148
      tests/cases/Database/SeriesArticle.php
  26. 82
      tests/cases/Database/SeriesCleanup.php
  27. 93
      tests/cases/Database/SeriesFeed.php
  28. 32
      tests/cases/Database/SeriesFolder.php
  29. 39
      tests/cases/Database/SeriesIcon.php
  30. 94
      tests/cases/Database/SeriesLabel.php
  31. 11
      tests/cases/Database/SeriesMeta.php
  32. 17
      tests/cases/Database/SeriesSession.php
  33. 104
      tests/cases/Database/SeriesSubscription.php
  34. 41
      tests/cases/Database/SeriesTag.php
  35. 18
      tests/cases/Database/SeriesToken.php
  36. 17
      tests/cases/Database/SeriesUser.php
  37. 2
      tests/cases/Db/BaseDriver.php
  38. 2
      tests/cases/Exception/TestException.php
  39. 8
      tests/cases/Feed/TestException.php
  40. 51
      tests/cases/ImportExport/TestImportExport.php
  41. 8
      tests/cases/Misc/TestContext.php
  42. 6
      tests/cases/Misc/TestQuery.php
  43. 1
      tests/cases/Misc/TestURL.php
  44. 3
      tests/cases/REST/Miniflux/TestV1.php
  45. 2
      tests/cases/REST/NextcloudNews/TestV1_2.php
  46. 17
      tests/cases/REST/TestREST.php
  47. 12
      tests/cases/REST/TinyTinyRSS/TestAPI.php
  48. 2
      tests/cases/REST/TinyTinyRSS/TestIcon.php
  49. 2
      tests/cases/Service/TestService.php
  50. 324
      tests/lib/AbstractTest.php
  51. 4
      tests/lib/DatabaseDrivers/MySQL.php
  52. 4
      tests/lib/DatabaseDrivers/MySQLPDO.php
  53. 2
      vendor-bin/csfixer/composer.json
  54. 621
      vendor-bin/csfixer/composer.lock
  55. 119
      vendor-bin/daux/composer.lock
  56. 367
      vendor-bin/phpunit/composer.lock
  57. 48
      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);

2
RoboFile.php

@ -164,7 +164,7 @@ class RoboFile extends \Robo\Tasks {
if (
(IS_WIN && (!exec(escapeshellarg($bin)." --help $blackhole", $junk, $status) || $status))
|| (!IS_WIN && (!exec("which ".escapeshellarg($bin)." $blackhole", $junk, $status) || $status))
) {
) {
return false;
}
}

2
UPGRADING

@ -9,7 +9,7 @@ usually prudent:
- Check for any changes to sample systemd unit or other init files
- If installing from source, update dependencies with:
`composer install -o --no-dev`
Upgrading from 0.10.2 to 0.10.3
=============================

12
composer.lock

@ -173,16 +173,16 @@
},
{
"name": "guzzlehttp/promises",
"version": "1.5.1",
"version": "1.5.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da"
"reference": "b94b2807d85443f9719887892882d0329d1e2598"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
"reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
"url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598",
"reference": "b94b2807d85443f9719887892882d0329d1e2598",
"shasum": ""
},
"require": {
@ -237,7 +237,7 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/1.5.1"
"source": "https://github.com/guzzle/promises/tree/1.5.2"
},
"funding": [
{
@ -253,7 +253,7 @@
"type": "tidelift"
}
],
"time": "2021-10-22T20:56:57+00:00"
"time": "2022-08-28T14:55:35+00:00"
},
{
"name": "guzzlehttp/psr7",

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;

50
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,7 +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
// compile the query
$integerType = $this->db->sqlToken("integer");
$q = new Query(
"WITH RECURSIVE
topmost(f_id, top) as (
@ -834,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
@ -855,8 +856,8 @@ class Database {
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");
@ -1613,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);
}
@ -1921,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());
@ -1952,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());
@ -2209,13 +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 {
$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

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

6
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 */
@ -83,6 +83,8 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
return '"utf8mb4_unicode_ci"';
case "asc":
return "";
case "integer":
return "signed integer";
default:
return $token;
}
@ -165,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) {

4
lib/Db/MySQL/ExceptionBuilder.php

@ -27,7 +27,7 @@ trait ExceptionBuilder {
public static function buildConnectionException($code, string $msg): array {
switch ($code) {
case 1045:
// @codeCoverageIgnoreStart
// @codeCoverageIgnoreStart
case 1043:
case 1044:
case 1046:
@ -48,7 +48,7 @@ trait ExceptionBuilder {
case 2018:
case 2026:
case 2028:
// @codeCoverageIgnoreEnd
// @codeCoverageIgnoreEnd
return [Exception::class, 'connectionFailure', ['engine' => "MySQL", 'message' => $msg]];
default:
return [Exception::class, 'engineErrorGeneral', $msg]; // @codeCoverageIgnore

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/Miniflux/V1.php

@ -212,7 +212,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
public function __construct() {
}
public static function respError($data, int $status = 400, array $headers = []): ResponseInterface {
assert(isset(Arsse::$lang) && Arsse::$lang instanceof \JKingWeb\Arsse\Lang, new \Exception("Language database must be initialized before use"));
$data = (array) $data;

8
lib/REST/NextcloudNews/V1_2.php

@ -293,7 +293,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
switch ($e->getCode()) {
// folder already exists
case 10236: return HTTP::respEmpty(409);
// folder name not acceptable
// folder name not acceptable
case 10231:
case 10232: return HTTP::respEmpty(422);
// other errors related to input
@ -324,9 +324,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
switch ($e->getCode()) {
// folder does not exist
case 10239: return HTTP::respEmpty(404);
// folder already exists
// folder already exists
case 10236: return HTTP::respEmpty(409);
// folder name not acceptable
// folder name not acceptable
case 10231:
case 10232: return HTTP::respEmpty(422);
// other errors related to input
@ -458,7 +458,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
switch ($e->getCode()) {
// subscription does not exist
case 10239: return HTTP::respEmpty(404);
// name is invalid
// name is invalid
case 10231:
case 10232: return HTTP::respEmpty(422);
// other errors related to input

2
lib/REST/TinyTinyRSS/API.php

@ -999,7 +999,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
switch ($e->getCode()) {
case 10236: // label already exists
// retrieve the ID of the existing label; duplicating a label silently returns the existing one
return $this->labelOut(Arsse::$db->labelPropertiesGet(Arsse::$user->id, $in['name'], true)['id']);
return $this->labelOut(Arsse::$db->labelPropertiesGet(Arsse::$user->id, $in['name'], true)['id']);
default: // other errors related to input
throw new Exception("INCORRECT_USAGE");
}

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

148
tests/cases/Database/SeriesArticle.php

@ -18,12 +18,8 @@ trait SeriesArticle {
protected function setUpSeriesArticle(): void {
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
],
'rows' => [
'columns' => ["id", "password", "num"],
'rows' => [
["jane.doe@example.com", "", 1],
["john.doe@example.com", "", 2],
["john.doe@example.org", "", 3],
@ -32,12 +28,8 @@ trait SeriesArticle {
],
],
'arsse_feeds' => [
'columns' => [
'id' => "int",
'url' => "str",
'title' => "str",
],
'rows' => [
'columns' => ["id", "url", "title"],
'rows' => [
[1,"http://example.com/1", "Feed 1"],
[2,"http://example.com/2", "Feed 2"],
[3,"http://example.com/3", "Feed 3"],
@ -54,13 +46,8 @@ trait SeriesArticle {
],
],
'arsse_folders' => [
'columns' => [
'id' => "int",
'owner' => "str",
'parent' => "int",
'name' => "str",
],
'rows' => [
'columns' => ["id", "owner", "parent", "name"],
'rows' => [
[1, "john.doe@example.com", null, "Technology"],
[2, "john.doe@example.com", 1, "Software"],
[3, "john.doe@example.com", 1, "Rocketry"],
@ -73,12 +60,8 @@ trait SeriesArticle {
],
],
'arsse_tags' => [
'columns' => [
'id' => "int",
'owner' => "str",
'name' => "str",
],
'rows' => [
'columns' => ["id", "owner", "name"],
'rows' => [
[1, "john.doe@example.com", "Technology"],
[2, "john.doe@example.com", "Software"],
[3, "john.doe@example.com", "Rocketry"],
@ -90,39 +73,28 @@ trait SeriesArticle {
],
],
'arsse_subscriptions' => [
'columns' => [
'id' => "int",
'owner' => "str",
'feed' => "int",
'folder' => "int",
'title' => "str",
'scrape' => "bool",
],
'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],
'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],
[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",
],
'rows' => [
'columns' => ["tag", "subscription", "assigned"],
'rows' => [
[1,3,1],
[1,4,1],
[2,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,12 +141,8 @@ trait SeriesArticle {
],
],
'arsse_enclosures' => [
'columns' => [
'article' => "int",
'url' => "str",
'type' => "str",
],
'rows' => [
'columns' => ["article", "url", "type"],
'rows' => [
[102,"http://example.com/text","text/plain"],
[103,"http://example.com/video","video/webm"],
[104,"http://example.com/image","image/svg+xml"],
@ -195,11 +151,8 @@ trait SeriesArticle {
],
],
'arsse_editions' => [
'columns' => [
'id' => "int",
'article' => "int",
],
'rows' => [
'columns' => ["id", "article"],
'rows' => [
[1,1],
[2,2],
[3,3],
@ -234,16 +187,8 @@ trait SeriesArticle {
],
],
'arsse_marks' => [
'columns' => [
'subscription' => "int",
'article' => "int",
'read' => "bool",
'starred' => "bool",
'modified' => "datetime",
'note' => "str",
'hidden' => "bool",
],
'rows' => [
'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],
[5, 20,0,1,'2005-01-01 00:00:00','',0],
@ -263,24 +208,17 @@ trait SeriesArticle {
],
],
'arsse_categories' => [ // author-supplied categories
'columns' => [
'article' => "int",
'name' => "str",
],
'rows' => [
'columns' => ["article", "name"],
'rows' => [
[19,"Fascinating"],
[19,"Logical"],
[20,"Interesting"],
[20,"Logical"],
],
],
'arsse_labels' => [
'columns' => [
'id' => "int",
'owner' => "str",
'name' => "str",
],
'rows' => [
'arsse_labels' => [ // labels applied to articles
'columns' => ["id", "owner", "name"],
'rows' => [
[1,"john.doe@example.com","Interesting"],
[2,"john.doe@example.com","Fascinating"],
[3,"jane.doe@example.com","Boring"],
@ -288,14 +226,8 @@ trait SeriesArticle {
],
],
'arsse_label_members' => [
'columns' => [
'label' => "int",
'article' => "int",
'subscription' => "int",
'assigned' => "bool",
'modified' => "datetime",
],
'rows' => [
'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'],
[1,19,5,1,'2000-01-01 00:00:00'],

82
tests/cases/Database/SeriesCleanup.php

@ -27,24 +27,15 @@ 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',
],
'rows' => [
'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",
],
'rows' => [
'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
["c", $daysago, $soon, "jane.doe@example.com"], // created more than a day ago, thus deleted
@ -53,13 +44,8 @@ trait SeriesCleanup {
],
],
'arsse_tokens' => [
'columns' => [
'id' => "str",
'class' => "str",
'user' => "str",
'expires' => "datetime",
],
'rows' => [
'columns' => ["id", "class", "user", "expires"],
'rows' => [
["80fa94c1a11f11e78667001e673b2560", "fever.login", "jane.doe@example.com", $faroff],
["27c6de8da13311e78667001e673b2560", "fever.login", "jane.doe@example.com", $weeksago], // expired
["ab3b3eb8a13311e78667001e673b2560", "class.class", "jane.doe@example.com", null],
@ -67,27 +53,16 @@ trait SeriesCleanup {
],
],
'arsse_icons' => [
'columns' => [
'id' => "int",
'url' => "str",
'orphaned' => "datetime",
],
'rows' => [
'columns' => ["id", "url", "orphaned"],
'rows' => [
[1,'http://localhost:8000/Icon/PNG',$daybefore],
[2,'http://localhost:8000/Icon/GIF',$daybefore],
[3,'http://localhost:8000/Icon/SVG1',null],
],
],
'arsse_feeds' => [
'columns' => [
'id' => "int",
'url' => "str",
'title' => "str",
'orphaned' => "datetime",
'size' => "int",
'icon' => "int",
],
'rows' => [
'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],
[3,"http://example.com/3","",null,0,1],
@ -95,12 +70,8 @@ trait SeriesCleanup {
],
],
'arsse_subscriptions' => [
'columns' => [
'id' => "int",
'owner' => "str",
'feed' => "int",
],
'rows' => [
'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],
// other subscriptions exist for article cleanup tests
@ -108,15 +79,8 @@ trait SeriesCleanup {
],
],
'arsse_articles' => [
'columns' => [
'id' => "int",
'feed' => "int",
'url_title_hash' => "str",
'url_content_hash' => "str",
'title_content_hash' => "str",
'modified' => "datetime",
],
'rows' => [
'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
[3,1,"","","",$weeksago], // is starred by one user, thus is kept
@ -129,11 +93,8 @@ trait SeriesCleanup {
],
],
'arsse_editions' => [
'columns' => [
'id' => "int",
'article' => "int",
],
'rows' => [
'columns' => ["id", "article"],
'rows' => [
[1,1],
[2,2],
[3,3],
@ -143,15 +104,8 @@ trait SeriesCleanup {
],
],
'arsse_marks' => [
'columns' => [
'article' => "int",
'subscription' => "int",
'read' => "bool",
'starred' => "bool",
'hidden' => "bool",
'modified' => "datetime",
],
'rows' => [
'columns' => ["article", "subscription", "read", "starred", "hidden", "modified"],
'rows' => [
[3,1,0,1,0,$weeksago],
[4,1,1,0,0,$daysago],
[6,1,1,0,0,$nowish],

93
tests/cases/Database/SeriesFeed.php

@ -17,24 +17,15 @@ trait SeriesFeed {
$now = gmdate("Y-m-d H:i:s", strtotime("now"));
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
],
'rows' => [
'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",
],
'rows' => [
'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==")],
// this actually contains the data of SVG2, which will lead to a row update when retieved
@ -42,18 +33,8 @@ 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",
],
'rows' => [
'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],
[3,"http://localhost:8000/Feed/Fetching/Error?code=404","Ack",0,"",$past,$now,0,null],
@ -67,14 +48,8 @@ trait SeriesFeed {
],
],
'arsse_subscriptions' => [
'columns' => [
'id' => "int",
'owner' => "str",
'feed' => "int",
'keep_rule' => "str",
'block_rule' => "str",
],
'rows' => [
'columns' => ["id", "owner", "feed", "keep_rule", "block_rule"],
'rows' => [
[1,'john.doe@example.com',1,null,'^Sport$'],
[2,'john.doe@example.com',2,"",null],
[3,'john.doe@example.com',3,'\w+',null],
@ -84,22 +59,8 @@ 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",
],
'rows' => [
'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],
[3,1,'http://example.com/3','Article title 3','','2000-01-03 00:00:00','2000-01-03 00:00:00','<p>Article content 3</p>','31a6594500a48b59fcc8a075ce82b946c9c3c782460d088bd7b8ef3ede97ad92','f74b06b240bd08abf4d3fdfc20dba6a6f6eb8b4f1a00e9a617efd63a87180a4b','b278380e984cefe63f0e412b88ffc9cb0befdfa06fdc00bace1da99a8daff406','ad622b31e739cd3a3f3c788991082cf4d2f7a8773773008e75f0572e58cd373b',$past],
@ -110,12 +71,8 @@ trait SeriesFeed {
],
],
'arsse_editions' => [
'columns' => [
'id' => "int",
'article' => "int",
'modified' => "datetime",
],
'rows' => [
'columns' => ["id", "article", "modified"],
'rows' => [
[1,1,$past],
[2,2,$past],
[3,3,$past],
@ -124,15 +81,8 @@ trait SeriesFeed {
],
],
'arsse_marks' => [
'columns' => [
'article' => "int",
'subscription' => "int",
'read' => "bool",
'starred' => "bool",
'hidden' => "bool",
'modified' => "datetime",
],
'rows' => [
'columns' => ["article", "subscription", "read", "starred", "hidden", "modified"],
'rows' => [
// Jane's marks
[1,6,1,0,0,$past],
[2,6,1,0,0,$past],
@ -146,21 +96,14 @@ trait SeriesFeed {
],
],
'arsse_enclosures' => [
'columns' => [
'article' => "int",
'url' => "str",
'type' => "str",
],
'rows' => [
'columns' => ["article", "url", "type"],
'rows' => [
[7,'http://example.com/png','image/png'],
],
],
'arsse_categories' => [
'columns' => [
'article' => "int",
'name' => "str",
],
'rows' => [
'columns' => ["article", "name"],
'rows' => [
[7,'Syrinx'],
],
],

32
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',
],
'rows' => [
'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,12 +40,8 @@ trait SeriesFolder {
],
],
'arsse_feeds' => [
'columns' => [
'id' => "int",
'url' => "str",
'title' => "str",
],
'rows' => [
'columns' => ["id", "url", "title"],
'rows' => [
[1,"http://example.com/1", "Feed 1"],
[2,"http://example.com/2", "Feed 2"],
[3,"http://example.com/3", "Feed 3"],
@ -71,13 +58,8 @@ trait SeriesFolder {
],
],
'arsse_subscriptions' => [
'columns' => [
'id' => "int",
'owner' => "str",
'feed' => "int",
'folder' => "int",
],
'rows' => [
'columns' => ["id", "owner", "feed", "folder"],
'rows' => [
[1, "john.doe@example.com",1, null],
[2, "john.doe@example.com",2, null],
[3, "john.doe@example.com",3, 1],

39
tests/cases/Database/SeriesIcon.php

@ -16,24 +16,15 @@ trait SeriesIcon {
$now = gmdate("Y-m-d H:i:s", strtotime("now"));
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
],
'rows' => [
'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",
],
'rows' => [
'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==")],
[3,'http://localhost:8000/Icon/SVG1','image/svg+xml','<svg xmlns="http://www.w3.org/2000/svg" width="900" height="600"><rect fill="#fff" height="600" width="900"/><circle fill="#bc002d" cx="450" cy="300" r="180"/></svg>'],
@ -41,18 +32,8 @@ 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",
],
'rows' => [
'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],
[3,"http://localhost:8000/Feed/Fetching/Error?code=404","Ack",0,"",$past,$now,0,3],
@ -61,12 +42,8 @@ trait SeriesIcon {
],
],
'arsse_subscriptions' => [
'columns' => [
'id' => "int",
'owner' => "str",
'feed' => "int",
],
'rows' => [
'columns' => ["id", "owner", "feed"],
'rows' => [
[1,'john.doe@example.com',1],
[2,'john.doe@example.com',2],
[3,'john.doe@example.com',3],

94
tests/cases/Database/SeriesLabel.php

@ -14,12 +14,8 @@ trait SeriesLabel {
protected function setUpSeriesLabel(): void {
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
],
'rows' => [
'columns' => ["id", "password", "num"],
'rows' => [
["jane.doe@example.com", "",1],
["john.doe@example.com", "",2],
["john.doe@example.org", "",3],
@ -27,13 +23,8 @@ trait SeriesLabel {
],
],
'arsse_folders' => [
'columns' => [
'id' => "int",
'owner' => "str",
'parent' => "int",
'name' => "str",
],
'rows' => [
'columns' => ["id", "owner", "parent", "name"],
'rows' => [
[1, "john.doe@example.com", null, "Technology"],
[2, "john.doe@example.com", 1, "Software"],
[3, "john.doe@example.com", 1, "Rocketry"],
@ -46,11 +37,8 @@ trait SeriesLabel {
],
],
'arsse_feeds' => [
'columns' => [
'id' => "int",
'url' => "str",
],
'rows' => [
'columns' => ["id", "url"],
'rows' => [
[1,"http://example.com/1"],
[2,"http://example.com/2"],
[3,"http://example.com/3"],
@ -67,13 +55,8 @@ trait SeriesLabel {
],
],
'arsse_subscriptions' => [
'columns' => [
'id' => "int",
'owner' => "str",
'feed' => "int",
'folder' => "int",
],
'rows' => [
'columns' => ["id", "owner", "feed", "folder"],
'rows' => [
[1,"john.doe@example.com",1,null],
[2,"john.doe@example.com",2,null],
[3,"john.doe@example.com",3,1],
@ -91,22 +74,8 @@ 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",
],
'rows' => [
'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"],
[3,2,null,null,null,null,null,null,null,"","","","2000-01-01T00:00:00Z"],
@ -135,12 +104,8 @@ trait SeriesLabel {
],
],
'arsse_enclosures' => [
'columns' => [
'article' => "int",
'url' => "str",
'type' => "str",
],
'rows' => [
'columns' => ["article", "url", "type"],
'rows' => [
[102,"http://example.com/text","text/plain"],
[103,"http://example.com/video","video/webm"],
[104,"http://example.com/image","image/svg+xml"],
@ -149,11 +114,8 @@ trait SeriesLabel {
],
],
'arsse_editions' => [
'columns' => [
'id' => "int",
'article' => "int",
],
'rows' => [
'columns' => ["id", "article"],
'rows' => [
[1,1],
[2,2],
[3,3],
@ -188,15 +150,8 @@ trait SeriesLabel {
],
],
'arsse_marks' => [
'columns' => [
'subscription' => "int",
'article' => "int",
'read' => "bool",
'starred' => "bool",
'modified' => "datetime",
'hidden' => "bool",
],
'rows' => [
'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],
[5, 20,0,1,'2010-01-01 00:00:00',0],
@ -213,12 +168,8 @@ trait SeriesLabel {
],
],
'arsse_labels' => [
'columns' => [
'id' => "int",
'owner' => "str",
'name' => "str",
],
'rows' => [
'columns' => ["id", "owner", "name"],
'rows' => [
[1,"john.doe@example.com","Interesting"],
[2,"john.doe@example.com","Fascinating"],
[3,"jane.doe@example.com","Boring"],
@ -226,13 +177,8 @@ trait SeriesLabel {
],
],
'arsse_label_members' => [
'columns' => [
'label' => "int",
'article' => "int",
'subscription' => "int",
'assigned' => "bool",
],
'rows' => [
'columns' => ["label", "article", "subscription", "assigned"],
'rows' => [
[1, 1,1,1],
[2, 1,1,1],
[1,19,5,1],

11
tests/cases/Database/SeriesMeta.php

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

17
tests/cases/Database/SeriesSession.php

@ -23,24 +23,15 @@ 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',
],
'rows' => [
'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",
],
'rows' => [
'columns' => ["id", "user", "created", "expires"],
'rows' => [
["80fa94c1a11f11e78667001e673b2560", "jane.doe@example.com", $past, $faroff],
["27c6de8da13311e78667001e673b2560", "jane.doe@example.com", $past, $past], // expired
["ab3b3eb8a13311e78667001e673b2560", "jane.doe@example.com", $old, $future], // too old

104
tests/cases/Database/SeriesSubscription.php

@ -15,12 +15,8 @@ trait SeriesSubscription {
public function setUpSeriesSubscription(): void {
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
],
'rows' => [
'columns' => ["id", "password", "num"],
'rows' => [
["jane.doe@example.com", "", 1],
["john.doe@example.com", "", 2],
["jill.doe@example.com", "", 3],
@ -28,13 +24,8 @@ trait SeriesSubscription {
],
],
'arsse_folders' => [
'columns' => [
'id' => "int",
'owner' => "str",
'parent' => "int",
'name' => "str",
],
'rows' => [
'columns' => ["id", "owner", "parent", "name"],
'rows' => [
[1, "john.doe@example.com", null, "Technology"],
[2, "john.doe@example.com", 1, "Software"],
[3, "john.doe@example.com", 1, "Rocketry"],
@ -44,28 +35,15 @@ trait SeriesSubscription {
],
],
'arsse_icons' => [
'columns' => [
'id' => "int",
'url' => "str",
'data' => "blob",
],
'rows' => [
'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",
],
'rows' => [
'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],
[3,"http://example.com/feed3", "Ack", "", "",strtotime("now + 1 hour"),strtotime("now + 1 hour"),2],
@ -73,19 +51,8 @@ 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",
],
'rows' => [
'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],
[3,"john.doe@example.com",3,"Ook",2,0,1,null,null,0],
@ -95,12 +62,8 @@ trait SeriesSubscription {
],
],
'arsse_tags' => [
'columns' => [
'id' => "int",
'owner' => "str",
'name' => "str",
],
'rows' => [
'columns' => ["id", "owner", "name"],
'rows' => [
[1,"john.doe@example.com","Interesting"],
[2,"john.doe@example.com","Fascinating"],
[3,"jane.doe@example.com","Boring"],
@ -108,12 +71,8 @@ trait SeriesSubscription {
],
],
'arsse_tag_members' => [
'columns' => [
'tag' => "int",
'subscription' => "int",
'assigned' => "bool",
],
'rows' => [
'columns' => ["tag", "subscription", "assigned"],
'rows' => [
[1,1,1],
[1,3,0],
[2,1,1],
@ -122,15 +81,8 @@ trait SeriesSubscription {
],
],
'arsse_articles' => [
'columns' => [
'id' => "int",
'feed' => "int",
'url_title_hash' => "str",
'url_content_hash' => "str",
'title_content_hash' => "str",
'title' => "str",
],
'rows' => [
'columns' => ["id", "feed", "url_title_hash", "url_content_hash", "title_content_hash", "title"],
'rows' => [
[1,2,"","","","Title 1"],
[2,2,"","","","Title 2"],
[3,2,"","","","Title 3"],
@ -142,11 +94,8 @@ trait SeriesSubscription {
],
],
'arsse_editions' => [
'columns' => [
'id' => "int",
'article' => "int",
],
'rows' => [
'columns' => ["id", "article"],
'rows' => [
[1,1],
[2,2],
[3,3],
@ -158,11 +107,8 @@ trait SeriesSubscription {
],
],
'arsse_categories' => [
'columns' => [
'article' => "int",
'name' => "str",
],
'rows' => [
'columns' => ["article", "name"],
'rows' => [
[1,"A"],
[2,"B"],
[4,"D"],
@ -173,14 +119,8 @@ trait SeriesSubscription {
],
],
'arsse_marks' => [
'columns' => [
'article' => "int",
'subscription' => "int",
'read' => "bool",
'starred' => "bool",
'hidden' => "bool",
],
'rows' => [
'columns' => ["article", "subscription", "read", "starred", "hidden"],
'rows' => [
[1,2,1,0,0],
[2,2,1,0,0],
[3,2,1,0,0],

41
tests/cases/Database/SeriesTag.php

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

18
tests/cases/Database/SeriesToken.php

@ -17,25 +17,15 @@ 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',
],
'rows' => [
'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",
],
'rows' => [
'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
["ab3b3eb8a13311e78667001e673b2560", "class.class", "jane.doe@example.com", null, null],

17
tests/cases/Database/SeriesUser.php

@ -12,25 +12,16 @@ trait SeriesUser {
protected function setUpSeriesUser(): void {
$this->data = [
'arsse_users' => [
'columns' => [
'id' => 'str',
'password' => 'str',
'num' => 'int',
'admin' => 'bool',
],
'rows' => [
'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],
["john.doe@example.com", "", 3, 0],
],
],
'arsse_user_meta' => [
'columns' => [
'owner' => "str",
'key' => "str",
'value' => "str",
],
'rows' => [
'columns' => ["owner", "key", "value"],
'rows' => [
["admin@example.net", "lang", "en"],
["admin@example.net", "tz", "America/Toronto"],
["admin@example.net", "sort_asc", "0"],

2
tests/cases/Db/BaseDriver.php

@ -384,6 +384,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
$greatest = $this->drv->sqlToken("GrEatESt");
$nocase = $this->drv->sqlToken("noCASE");
$like = $this->drv->sqlToken("liKe");
$integer = $this->drv->sqlToken("InTEGer");
$asc = $this->drv->sqlToken("asc");
$desc = $this->drv->sqlToken("desc");
$least = $this->drv->sqlToken("leASt");
@ -394,6 +395,7 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest {
$this->assertSame("Z", $this->drv->query("SELECT $greatest('Z', 'A')")->getValue());
$this->assertSame("Z", $this->drv->query("SELECT 'Z' collate $nocase")->getValue());
$this->assertSame("Z", $this->drv->query("SELECT 'Z' where 'Z' $like 'z'")->getValue());
$this->assertEquals(1, $this->drv->query("SELECT CAST((1=1) as $integer)")->getValue());
$this->assertEquals([null, 1, 2], array_column($this->drv->query("SELECT 1 as t union select null as t union select 2 as t order by t $asc")->getAll(), "t"));
$this->assertEquals([2, 1, null], array_column($this->drv->query("SELECT 1 as t union select null as t union select 2 as t order by t $desc")->getAll(), "t"));
}

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"],
];
}

51
tests/cases/ImportExport/TestImportExport.php

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

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

@ -18,7 +18,6 @@ use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Db\Transaction;
use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\REST\Miniflux\V1;
use JKingWeb\Arsse\REST\Miniflux\ErrorResponse;
use JKingWeb\Arsse\Feed\Exception as FeedException;
use JKingWeb\Arsse\ImportExport\Exception as ImportException;
use JKingWeb\Arsse\ImportExport\OPML;
@ -91,7 +90,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

@ -327,7 +327,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) {

17
tests/cases/REST/TestREST.php

@ -21,7 +21,6 @@ use GuzzleHttp\Psr7\ServerRequest;
/** @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);
@ -60,7 +59,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);
@ -94,7 +93,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
public function testSendAuthenticationChallenges(): void {
self::setConf();
$r = new REST();
$r = new REST;
$in = HTTP::respEmpty(401);
$exp = $in->withHeader("WWW-Authenticate", 'Basic realm="OOK", charset="UTF-8"');
$act = $r->challenge($in, "OOK");
@ -106,7 +105,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);
}
@ -187,7 +186,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 = HTTP::respEmpty(204, $resHeaders);
$exp = HTTP::respEmpty(204, $expHeaders);
@ -317,13 +316,13 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideMockRequests(): iterable {
return [
[new ServerRequest("GET", "/index.php/apps/news/api/v1-2/feeds"), "GET", true, NCN::class, "/feeds"],
[new ServerRequest("GET", "/index.php/apps/news/api/v1-2/feeds"), "GET", true, NCN::class, "/feeds"],
[new ServerRequest("get", "/index.php/apps/news/api/v1-2/feeds"), "GET", true, NCN::class, "/feeds"],
[new ServerRequest("GET", "/index.php/apps/news/api/v1-2/feeds"), "GET", true, NCN::class, "/feeds"],
[new ServerRequest("GET", "/index.php/apps/news/api/v1-2/feeds"), "GET", true, NCN::class, "/feeds"],
[new ServerRequest("get", "/index.php/apps/news/api/v1-2/feeds"), "GET", true, NCN::class, "/feeds"],
[new ServerRequest("head", "/index.php/apps/news/api/v1-2/feeds"), "GET", true, NCN::class, "/feeds"],
[new ServerRequest("POST", "/tt-rss/api/"), "POST", true, TTRSS::class, "/"],
[new ServerRequest("HEAD", "/no/such/api/"), "GET", false],
[new ServerRequest("GET", "/no/such/api/"), "GET", false],
[new ServerRequest("GET", "/no/such/api/"), "GET", false],
];
}
}

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

@ -146,7 +146,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 {
@ -1523,11 +1523,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 {

324
tests/lib/AbstractTest.php

@ -17,7 +17,6 @@ use JKingWeb\Arsse\Db\Driver;
use JKingWeb\Arsse\Db\Result;
use JKingWeb\Arsse\Factory;
use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\ValueInfo;
use JKingWeb\Arsse\Misc\URL;
use JKingWeb\Arsse\Misc\HTTP;
use Psr\Http\Message\MessageInterface;
@ -30,6 +29,152 @@ use GuzzleHttp\Psr7\ServerRequest;
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;
@ -53,7 +198,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;
}
}
@ -61,17 +206,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);
}
@ -252,14 +397,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) {
@ -271,70 +435,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];
}
}
}
@ -346,10 +544,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"
}
}

621
vendor-bin/csfixer/composer.lock

File diff suppressed because it is too large

119
vendor-bin/daux/composer.lock

@ -83,16 +83,16 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "7.4.5",
"version": "7.5.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82"
"reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/1dd98b0564cb3f6bd16ce683cb755f94c10fbd82",
"reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba",
"reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba",
"shasum": ""
},
"require": {
@ -107,10 +107,10 @@
"psr/http-client-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4.1",
"bamarni/composer-bin-plugin": "^1.8.1",
"ext-curl": "*",
"php-http/client-integration-tests": "^3.0",
"phpunit/phpunit": "^8.5.5 || ^9.3.5",
"phpunit/phpunit": "^8.5.29 || ^9.5.23",
"psr/log": "^1.1 || ^2.0 || ^3.0"
},
"suggest": {
@ -120,8 +120,12 @@
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
},
"branch-alias": {
"dev-master": "7.4-dev"
"dev-master": "7.5-dev"
}
},
"autoload": {
@ -187,7 +191,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.4.5"
"source": "https://github.com/guzzle/guzzle/tree/7.5.0"
},
"funding": [
{
@ -203,20 +207,20 @@
"type": "tidelift"
}
],
"time": "2022-06-20T22:16:13+00:00"
"time": "2022-08-28T15:39:27+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "1.5.1",
"version": "1.5.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da"
"reference": "b94b2807d85443f9719887892882d0329d1e2598"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
"reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
"url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598",
"reference": "b94b2807d85443f9719887892882d0329d1e2598",
"shasum": ""
},
"require": {
@ -271,7 +275,7 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/1.5.1"
"source": "https://github.com/guzzle/promises/tree/1.5.2"
},
"funding": [
{
@ -287,20 +291,20 @@
"type": "tidelift"
}
],
"time": "2021-10-22T20:56:57+00:00"
"time": "2022-08-28T14:55:35+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "2.4.0",
"version": "2.4.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "13388f00956b1503577598873fffb5ae994b5737"
"reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/13388f00956b1503577598873fffb5ae994b5737",
"reference": "13388f00956b1503577598873fffb5ae994b5737",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/69568e4293f4fa993f3b0e51c9723e1e17c41379",
"reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379",
"shasum": ""
},
"require": {
@ -314,15 +318,19 @@
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4.1",
"bamarni/composer-bin-plugin": "^1.8.1",
"http-interop/http-factory-tests": "^0.9",
"phpunit/phpunit": "^8.5.8 || ^9.3.10"
"phpunit/phpunit": "^8.5.29 || ^9.5.23"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
},
"branch-alias": {
"dev-master": "2.4-dev"
}
@ -386,7 +394,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.4.0"
"source": "https://github.com/guzzle/psr7/tree/2.4.1"
},
"funding": [
{
@ -402,7 +410,7 @@
"type": "tidelift"
}
],
"time": "2022-06-20T21:43:11+00:00"
"time": "2022-08-28T14:45:39+00:00"
},
{
"name": "league/commonmark",
@ -898,16 +906,16 @@
},
{
"name": "symfony/console",
"version": "v5.4.11",
"version": "v5.4.12",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "535846c7ee6bc4dd027ca0d93220601456734b10"
"reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/535846c7ee6bc4dd027ca0d93220601456734b10",
"reference": "535846c7ee6bc4dd027ca0d93220601456734b10",
"url": "https://api.github.com/repos/symfony/console/zipball/c072aa8f724c3af64e2c7a96b796a4863d24dba1",
"reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1",
"shasum": ""
},
"require": {
@ -977,7 +985,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v5.4.11"
"source": "https://github.com/symfony/console/tree/v5.4.12"
},
"funding": [
{
@ -993,7 +1001,7 @@
"type": "tidelift"
}
],
"time": "2022-07-22T10:42:43+00:00"
"time": "2022-08-17T13:18:05+00:00"
},
{
"name": "symfony/deprecation-contracts",
@ -1064,16 +1072,16 @@
},
{
"name": "symfony/http-foundation",
"version": "v5.4.11",
"version": "v5.4.12",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
"reference": "0a5868e0999e9d47859ba3d918548ff6943e6389"
"reference": "f4bfe9611b113b15d98a43da68ec9b5a00d56791"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/0a5868e0999e9d47859ba3d918548ff6943e6389",
"reference": "0a5868e0999e9d47859ba3d918548ff6943e6389",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/f4bfe9611b113b15d98a43da68ec9b5a00d56791",
"reference": "f4bfe9611b113b15d98a43da68ec9b5a00d56791",
"shasum": ""
},
"require": {
@ -1085,8 +1093,11 @@
"require-dev": {
"predis/predis": "~1.0",
"symfony/cache": "^4.4|^5.0|^6.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/expression-language": "^4.4|^5.0|^6.0",
"symfony/mime": "^4.4|^5.0|^6.0"
"symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4",
"symfony/mime": "^4.4|^5.0|^6.0",
"symfony/rate-limiter": "^5.2|^6.0"
},
"suggest": {
"symfony/mime": "To use the file extension guesser"
@ -1117,7 +1128,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.11"
"source": "https://github.com/symfony/http-foundation/tree/v5.4.12"
},
"funding": [
{
@ -1133,20 +1144,20 @@
"type": "tidelift"
}
],
"time": "2022-07-20T13:00:38+00:00"
"time": "2022-08-19T07:33:17+00:00"
},
{
"name": "symfony/mime",
"version": "v5.4.11",
"version": "v5.4.12",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
"reference": "3cd175cdcdb6db2e589e837dd46aff41027d9830"
"reference": "03876e9c5a36f5b45e7d9a381edda5421eff8a90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/mime/zipball/3cd175cdcdb6db2e589e837dd46aff41027d9830",
"reference": "3cd175cdcdb6db2e589e837dd46aff41027d9830",
"url": "https://api.github.com/repos/symfony/mime/zipball/03876e9c5a36f5b45e7d9a381edda5421eff8a90",
"reference": "03876e9c5a36f5b45e7d9a381edda5421eff8a90",
"shasum": ""
},
"require": {
@ -1200,7 +1211,7 @@
"mime-type"
],
"support": {
"source": "https://github.com/symfony/mime/tree/v5.4.11"
"source": "https://github.com/symfony/mime/tree/v5.4.12"
},
"funding": [
{
@ -1216,7 +1227,7 @@
"type": "tidelift"
}
],
"time": "2022-07-20T11:34:24+00:00"
"time": "2022-08-19T14:24:03+00:00"
},
{
"name": "symfony/polyfill-ctype",
@ -2109,16 +2120,16 @@
},
{
"name": "symfony/string",
"version": "v6.1.3",
"version": "v6.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "f35241f45c30bcd9046af2bb200a7086f70e1d6b"
"reference": "290972cad7b364e3befaa74ba0ec729800fb161c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/f35241f45c30bcd9046af2bb200a7086f70e1d6b",
"reference": "f35241f45c30bcd9046af2bb200a7086f70e1d6b",
"url": "https://api.github.com/repos/symfony/string/zipball/290972cad7b364e3befaa74ba0ec729800fb161c",
"reference": "290972cad7b364e3befaa74ba0ec729800fb161c",
"shasum": ""
},
"require": {
@ -2174,7 +2185,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v6.1.3"
"source": "https://github.com/symfony/string/tree/v6.1.4"
},
"funding": [
{
@ -2190,20 +2201,20 @@
"type": "tidelift"
}
],
"time": "2022-07-27T15:50:51+00:00"
"time": "2022-08-12T18:05:43+00:00"
},
{
"name": "symfony/yaml",
"version": "v5.4.11",
"version": "v5.4.12",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "05d4ea560f3402c6c116afd99fdc66e60eda227e"
"reference": "7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/05d4ea560f3402c6c116afd99fdc66e60eda227e",
"reference": "05d4ea560f3402c6c116afd99fdc66e60eda227e",
"url": "https://api.github.com/repos/symfony/yaml/zipball/7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c",
"reference": "7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c",
"shasum": ""
},
"require": {
@ -2249,7 +2260,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v5.4.11"
"source": "https://github.com/symfony/yaml/tree/v5.4.12"
},
"funding": [
{
@ -2265,7 +2276,7 @@
"type": "tidelift"
}
],
"time": "2022-06-27T16:58:25+00:00"
"time": "2022-08-02T15:52:22+00:00"
},
{
"name": "webuni/front-matter",

367
vendor-bin/phpunit/composer.lock

@ -448,16 +448,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.14.0",
"version": "v4.15.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1"
"reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1",
"reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900",
"reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900",
"shasum": ""
},
"require": {
@ -498,9 +498,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1"
},
"time": "2022-05-31T20:59:12+00:00"
"time": "2022-09-04T07:30:47+00:00"
},
{
"name": "phar-io/manifest",
@ -613,252 +613,25 @@
},
"time": "2022-02-21T01:04:05+00:00"
},
{
"name": "phpdocumentor/reflection-common",
"version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-2.x": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jaap van Otterdijk",
"email": "opensource@ijaap.nl"
}
],
"description": "Common reflection classes used by phpdocumentor to reflect the code structure",
"homepage": "http://www.phpdoc.org",
"keywords": [
"FQSEN",
"phpDocumentor",
"phpdoc",
"reflection",
"static analysis"
],
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
"source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
},
"time": "2020-06-27T09:03:43+00:00"
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "5.3.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
"reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
"shasum": ""
},
"require": {
"ext-filter": "*",
"php": "^7.2 || ^8.0",
"phpdocumentor/reflection-common": "^2.2",
"phpdocumentor/type-resolver": "^1.3",
"webmozart/assert": "^1.9.1"
},
"require-dev": {
"mockery/mockery": "~1.3.2",
"psalm/phar": "^4.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.x-dev"
}
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike van Riel",
"email": "me@mikevanriel.com"
},
{
"name": "Jaap van Otterdijk",
"email": "account@ijaap.nl"
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0"
},
"time": "2021-10-19T17:43:47+00:00"
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.6.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "77a32518733312af16a44300404e945338981de3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3",
"reference": "77a32518733312af16a44300404e945338981de3",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"phpdocumentor/reflection-common": "^2.0"
},
"require-dev": {
"ext-tokenizer": "*",
"psalm/phar": "^4.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-1.x": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike van Riel",
"email": "me@mikevanriel.com"
}
],
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1"
},
"time": "2022-03-15T21:29:03+00:00"
},
{
"name": "phpspec/prophecy",
"version": "v1.15.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
"reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.2",
"php": "^7.2 || ~8.0, <8.2",
"phpdocumentor/reflection-docblock": "^5.2",
"sebastian/comparator": "^3.0 || ^4.0",
"sebastian/recursion-context": "^3.0 || ^4.0"
},
"require-dev": {
"phpspec/phpspec": "^6.0 || ^7.0",
"phpunit/phpunit": "^8.0 || ^9.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Prophecy\\": "src/Prophecy"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Konstantin Kudryashov",
"email": "ever.zet@gmail.com",
"homepage": "http://everzet.com"
},
{
"name": "Marcello Duarte",
"email": "marcello.duarte@gmail.com"
}
],
"description": "Highly opinionated mocking framework for PHP 5.3+",
"homepage": "https://github.com/phpspec/prophecy",
"keywords": [
"Double",
"Dummy",
"fake",
"mock",
"spy",
"stub"
],
"support": {
"issues": "https://github.com/phpspec/prophecy/issues",
"source": "https://github.com/phpspec/prophecy/tree/v1.15.0"
},
"time": "2021-12-08T12:19:24+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "9.2.15",
"version": "9.2.17",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f"
"reference": "aa94dc41e8661fe90c7316849907cba3007b10d8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f",
"reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8",
"reference": "aa94dc41e8661fe90c7316849907cba3007b10d8",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^4.13.0",
"nikic/php-parser": "^4.14",
"php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.2",
@ -907,7 +680,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17"
},
"funding": [
{
@ -915,7 +688,7 @@
"type": "github"
}
],
"time": "2022-03-07T09:28:20+00:00"
"time": "2022-08-30T12:24:04+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -1160,16 +933,16 @@
},
{
"name": "phpunit/phpunit",
"version": "9.5.21",
"version": "9.5.24",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1"
"reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e32b76be457de00e83213528f6bb37e2a38fcb1",
"reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d0aa6097bef9fd42458a9b3c49da32c6ce6129c5",
"reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5",
"shasum": ""
},
"require": {
@ -1184,7 +957,6 @@
"phar-io/manifest": "^2.0.3",
"phar-io/version": "^3.0.2",
"php": ">=7.3",
"phpspec/prophecy": "^1.12.1",
"phpunit/php-code-coverage": "^9.2.13",
"phpunit/php-file-iterator": "^3.0.5",
"phpunit/php-invoker": "^3.1.1",
@ -1199,12 +971,9 @@
"sebastian/global-state": "^5.0.1",
"sebastian/object-enumerator": "^4.0.3",
"sebastian/resource-operations": "^3.0.3",
"sebastian/type": "^3.0",
"sebastian/type": "^3.1",
"sebastian/version": "^3.0.2"
},
"require-dev": {
"phpspec/prophecy-phpunit": "^2.0.1"
},
"suggest": {
"ext-soap": "*",
"ext-xdebug": "*"
@ -1246,7 +1015,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.21"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.24"
},
"funding": [
{
@ -1258,7 +1027,7 @@
"type": "github"
}
],
"time": "2022-06-19T12:14:25+00:00"
"time": "2022-08-30T07:42:16+00:00"
},
{
"name": "sebastian/cli-parser",
@ -1429,16 +1198,16 @@
},
{
"name": "sebastian/comparator",
"version": "4.0.6",
"version": "4.0.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "55f4261989e546dc112258c7a75935a81a7ce382"
"reference": "fa0f136dd2334583309d32b62544682ee972b51a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382",
"reference": "55f4261989e546dc112258c7a75935a81a7ce382",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a",
"reference": "fa0f136dd2334583309d32b62544682ee972b51a",
"shasum": ""
},
"require": {
@ -1491,7 +1260,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6"
"source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8"
},
"funding": [
{
@ -1499,7 +1268,7 @@
"type": "github"
}
],
"time": "2020-10-26T15:49:45+00:00"
"time": "2022-09-14T12:41:17+00:00"
},
{
"name": "sebastian/complexity",
@ -1689,16 +1458,16 @@
},
{
"name": "sebastian/exporter",
"version": "4.0.4",
"version": "4.0.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9"
"reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9",
"reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
"reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
"shasum": ""
},
"require": {
@ -1754,7 +1523,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
"source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4"
"source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5"
},
"funding": [
{
@ -1762,7 +1531,7 @@
"type": "github"
}
],
"time": "2021-11-11T14:18:36+00:00"
"time": "2022-09-14T06:03:37+00:00"
},
{
"name": "sebastian/global-state",
@ -2117,16 +1886,16 @@
},
{
"name": "sebastian/type",
"version": "3.0.0",
"version": "3.2.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
"reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad"
"reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad",
"reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e",
"reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e",
"shasum": ""
},
"require": {
@ -2138,7 +1907,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
"dev-master": "3.2-dev"
}
},
"autoload": {
@ -2161,7 +1930,7 @@
"homepage": "https://github.com/sebastianbergmann/type",
"support": {
"issues": "https://github.com/sebastianbergmann/type/issues",
"source": "https://github.com/sebastianbergmann/type/tree/3.0.0"
"source": "https://github.com/sebastianbergmann/type/tree/3.2.0"
},
"funding": [
{
@ -2169,7 +1938,7 @@
"type": "github"
}
],
"time": "2022-03-15T09:54:48+00:00"
"time": "2022-09-12T14:47:03+00:00"
},
{
"name": "sebastian/version",
@ -2274,64 +2043,6 @@
],
"time": "2021-07-28T10:34:58+00:00"
},
{
"name": "webmozart/assert",
"version": "1.11.0",
"source": {
"type": "git",
"url": "https://github.com/webmozarts/assert.git",
"reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991",
"reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"php": "^7.2 || ^8.0"
},
"conflict": {
"phpstan/phpstan": "<0.12.20",
"vimeo/psalm": "<4.6.1 || 4.6.2"
},
"require-dev": {
"phpunit/phpunit": "^8.5.13"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.10-dev"
}
},
"autoload": {
"psr-4": {
"Webmozart\\Assert\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
}
],
"description": "Assertions to validate method input/output with nice error messages.",
"keywords": [
"assert",
"check",
"validate"
],
"support": {
"issues": "https://github.com/webmozarts/assert/issues",
"source": "https://github.com/webmozarts/assert/tree/1.11.0"
},
"time": "2022-06-03T18:03:27+00:00"
},
{
"name": "webmozart/glob",
"version": "4.6.0",

48
vendor-bin/robo/composer.lock

@ -1070,16 +1070,16 @@
},
{
"name": "symfony/console",
"version": "v6.1.3",
"version": "v6.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "43fcb5c5966b43c56bcfa481368d90d748936ab8"
"reference": "7fccea8728aa2d431a6725b02b3ce759049fc84d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/43fcb5c5966b43c56bcfa481368d90d748936ab8",
"reference": "43fcb5c5966b43c56bcfa481368d90d748936ab8",
"url": "https://api.github.com/repos/symfony/console/zipball/7fccea8728aa2d431a6725b02b3ce759049fc84d",
"reference": "7fccea8728aa2d431a6725b02b3ce759049fc84d",
"shasum": ""
},
"require": {
@ -1146,7 +1146,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v6.1.3"
"source": "https://github.com/symfony/console/tree/v6.1.4"
},
"funding": [
{
@ -1162,7 +1162,7 @@
"type": "tidelift"
}
],
"time": "2022-07-22T14:17:57+00:00"
"time": "2022-08-26T10:32:31+00:00"
},
{
"name": "symfony/deprecation-contracts",
@ -1395,16 +1395,16 @@
},
{
"name": "symfony/filesystem",
"version": "v6.1.3",
"version": "v6.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "c780e677cddda78417fa5187a7c6cd2f21110db9"
"reference": "3f39c04d2630c34019907b02f85672dac99f8659"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/c780e677cddda78417fa5187a7c6cd2f21110db9",
"reference": "c780e677cddda78417fa5187a7c6cd2f21110db9",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/3f39c04d2630c34019907b02f85672dac99f8659",
"reference": "3f39c04d2630c34019907b02f85672dac99f8659",
"shasum": ""
},
"require": {
@ -1438,7 +1438,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v6.1.3"
"source": "https://github.com/symfony/filesystem/tree/v6.1.4"
},
"funding": [
{
@ -1454,7 +1454,7 @@
"type": "tidelift"
}
],
"time": "2022-07-20T14:45:06+00:00"
"time": "2022-08-02T16:17:38+00:00"
},
{
"name": "symfony/finder",
@ -1998,16 +1998,16 @@
},
{
"name": "symfony/string",
"version": "v6.1.3",
"version": "v6.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "f35241f45c30bcd9046af2bb200a7086f70e1d6b"
"reference": "290972cad7b364e3befaa74ba0ec729800fb161c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/f35241f45c30bcd9046af2bb200a7086f70e1d6b",
"reference": "f35241f45c30bcd9046af2bb200a7086f70e1d6b",
"url": "https://api.github.com/repos/symfony/string/zipball/290972cad7b364e3befaa74ba0ec729800fb161c",
"reference": "290972cad7b364e3befaa74ba0ec729800fb161c",
"shasum": ""
},
"require": {
@ -2063,7 +2063,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v6.1.3"
"source": "https://github.com/symfony/string/tree/v6.1.4"
},
"funding": [
{
@ -2079,20 +2079,20 @@
"type": "tidelift"
}
],
"time": "2022-07-27T15:50:51+00:00"
"time": "2022-08-12T18:05:43+00:00"
},
{
"name": "symfony/yaml",
"version": "v6.1.3",
"version": "v6.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "cc48dd42ae1201abced04ae38284e23ce2d2d8f3"
"reference": "86ee4d8fa594ed45e40d86eedfda1bcb66c8d919"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/cc48dd42ae1201abced04ae38284e23ce2d2d8f3",
"reference": "cc48dd42ae1201abced04ae38284e23ce2d2d8f3",
"url": "https://api.github.com/repos/symfony/yaml/zipball/86ee4d8fa594ed45e40d86eedfda1bcb66c8d919",
"reference": "86ee4d8fa594ed45e40d86eedfda1bcb66c8d919",
"shasum": ""
},
"require": {
@ -2137,7 +2137,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v6.1.3"
"source": "https://github.com/symfony/yaml/tree/v6.1.4"
},
"funding": [
{
@ -2153,7 +2153,7 @@
"type": "tidelift"
}
],
"time": "2022-07-20T14:45:06+00:00"
"time": "2022-08-02T16:17:38+00:00"
}
],
"aliases": [],

Loading…
Cancel
Save