Browse Source

Merge branch 'master' into dbtest

dbtest
J. King 2 years ago
parent
commit
a77b47cd59
  1. 3
      CHANGELOG
  2. 2
      RoboFile.php
  3. 7
      UPGRADING
  4. 2
      composer.json
  5. 240
      composer.lock
  6. 2
      lib/Arsse.php
  7. 4
      lib/Db/MySQL/ExceptionBuilder.php
  8. 27
      lib/Misc/HTTP.php
  9. 10
      lib/REST.php
  10. 17
      lib/REST/Fever/API.php
  11. 19
      lib/REST/Miniflux/ErrorResponse.php
  12. 11
      lib/REST/Miniflux/Status.php
  13. 216
      lib/REST/Miniflux/V1.php
  14. 142
      lib/REST/NextcloudNews/V1_2.php
  15. 11
      lib/REST/NextcloudNews/Versions.php
  16. 21
      lib/REST/TinyTinyRSS/API.php
  17. 14
      lib/REST/TinyTinyRSS/Icon.php
  18. 2
      locale/en.php
  19. 28
      tests/cases/Database/SeriesArticle.php
  20. 18
      tests/cases/Database/SeriesCleanup.php
  21. 18
      tests/cases/Database/SeriesFeed.php
  22. 6
      tests/cases/Database/SeriesFolder.php
  23. 8
      tests/cases/Database/SeriesIcon.php
  24. 20
      tests/cases/Database/SeriesLabel.php
  25. 2
      tests/cases/Database/SeriesMeta.php
  26. 4
      tests/cases/Database/SeriesSession.php
  27. 22
      tests/cases/Database/SeriesSubscription.php
  28. 10
      tests/cases/Database/SeriesTag.php
  29. 4
      tests/cases/Database/SeriesToken.php
  30. 4
      tests/cases/Database/SeriesUser.php
  31. 12
      tests/cases/ImportExport/TestImportExport.php
  32. 27
      tests/cases/Misc/TestHTTP.php
  33. 50
      tests/cases/REST/Fever/TestAPI.php
  34. 22
      tests/cases/REST/Miniflux/TestErrorResponse.php
  35. 17
      tests/cases/REST/Miniflux/TestStatus.php
  36. 463
      tests/cases/REST/Miniflux/TestV1.php
  37. 149
      tests/cases/REST/NextcloudNews/TestV1_2.php
  38. 11
      tests/cases/REST/NextcloudNews/TestVersions.php
  39. 69
      tests/cases/REST/TestREST.php
  40. 57
      tests/cases/REST/TinyTinyRSS/TestAPI.php
  41. 24
      tests/cases/REST/TinyTinyRSS/TestIcon.php
  42. 36
      tests/lib/AbstractTest.php
  43. 1
      tests/phpunit.dist.xml
  44. 313
      vendor-bin/csfixer/composer.lock
  45. 277
      vendor-bin/daux/composer.lock
  46. 460
      vendor-bin/phpunit/composer.lock
  47. 172
      vendor-bin/robo/composer.lock

3
CHANGELOG

@ -1,4 +1,4 @@
Version 0.1?.? (2022-??-??) Version 0.10.3 (2022-09-14)
=========================== ===========================
Bug fixes: Bug fixes:
@ -6,6 +6,7 @@ Bug fixes:
- Allow multiple date ranges in search strings in Tiny Tiny RSS - Allow multiple date ranges in search strings in Tiny Tiny RSS
- Honour user time zone when interpreting search strings in Tiny Tiny RSS - Honour user time zone when interpreting search strings in Tiny Tiny RSS
- Perform MySQL table maintenance more reliably - Perform MySQL table maintenance more reliably
- Address CVE-2022-31090, CVE-2022-31091, CVE-2022-29248, and CVE-2022-31109
Version 0.10.2 (2022-04-04) Version 0.10.2 (2022-04-04)
=========================== ===========================

2
RoboFile.php

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

7
UPGRADING

@ -11,6 +11,13 @@ usually prudent:
`composer install -o --no-dev` `composer install -o --no-dev`
Upgrading from 0.10.2 to 0.10.3
=============================
- The following Composer dependencies have been removed:
- laminas/laminas-diactoros
Upgrading from 0.8.5 to 0.9.0 Upgrading from 0.8.5 to 0.9.0
============================= =============================

2
composer.json

@ -28,7 +28,7 @@
"hosteurope/password-generator": "1.*", "hosteurope/password-generator": "1.*",
"docopt/docopt": "1.*", "docopt/docopt": "1.*",
"jkingweb/druuid": "3.*", "jkingweb/druuid": "3.*",
"laminas/laminas-diactoros": "2.*", "guzzlehttp/psr7": "1.*",
"laminas/laminas-httphandlerrunner": "1.*" "laminas/laminas-httphandlerrunner": "1.*"
}, },
"require-dev": { "require-dev": {

240
composer.lock

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "c658930fbc56b2b2cf646e34c6a8d8d3", "content-hash": "2671d9010a4ac73e877838baf3586df2",
"packages": [ "packages": [
{ {
"name": "docopt/docopt", "name": "docopt/docopt",
@ -58,24 +58,24 @@
}, },
{ {
"name": "guzzlehttp/guzzle", "name": "guzzlehttp/guzzle",
"version": "6.5.6", "version": "6.5.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/guzzle.git", "url": "https://github.com/guzzle/guzzle.git",
"reference": "f092dd734083473658de3ee4bef093ed77d2689c" "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/f092dd734083473658de3ee4bef093ed77d2689c", "url": "https://api.github.com/repos/guzzle/guzzle/zipball/a52f0440530b54fa079ce76e8c5d196a42cad981",
"reference": "f092dd734083473658de3ee4bef093ed77d2689c", "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-json": "*", "ext-json": "*",
"guzzlehttp/promises": "^1.0", "guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.6.1", "guzzlehttp/psr7": "^1.9",
"php": ">=5.5", "php": ">=5.5",
"symfony/polyfill-intl-idn": "^1.17.0" "symfony/polyfill-intl-idn": "^1.17"
}, },
"require-dev": { "require-dev": {
"ext-curl": "*", "ext-curl": "*",
@ -153,7 +153,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/guzzle/issues", "issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/6.5.6" "source": "https://github.com/guzzle/guzzle/tree/6.5.8"
}, },
"funding": [ "funding": [
{ {
@ -169,20 +169,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-25T13:19:12+00:00" "time": "2022-06-20T22:16:07+00:00"
}, },
{ {
"name": "guzzlehttp/promises", "name": "guzzlehttp/promises",
"version": "1.5.1", "version": "1.5.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/promises.git", "url": "https://github.com/guzzle/promises.git",
"reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" "reference": "b94b2807d85443f9719887892882d0329d1e2598"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598",
"reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", "reference": "b94b2807d85443f9719887892882d0329d1e2598",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -237,7 +237,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/promises/issues", "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": [ "funding": [
{ {
@ -253,20 +253,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-10-22T20:56:57+00:00" "time": "2022-08-28T14:55:35+00:00"
}, },
{ {
"name": "guzzlehttp/psr7", "name": "guzzlehttp/psr7",
"version": "1.8.5", "version": "1.9.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/psr7.git", "url": "https://github.com/guzzle/psr7.git",
"reference": "337e3ad8e5716c15f9657bd214d16cc5e69df268" "reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/337e3ad8e5716c15f9657bd214d16cc5e69df268", "url": "https://api.github.com/repos/guzzle/psr7/zipball/e98e3e6d4f86621a9b75f623996e6bbdeb4b9318",
"reference": "337e3ad8e5716c15f9657bd214d16cc5e69df268", "reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -287,7 +287,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.7-dev" "dev-master": "1.9-dev"
} }
}, },
"autoload": { "autoload": {
@ -347,7 +347,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/psr7/issues", "issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/1.8.5" "source": "https://github.com/guzzle/psr7/tree/1.9.0"
}, },
"funding": [ "funding": [
{ {
@ -363,7 +363,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-03-20T21:51:18+00:00" "time": "2022-06-20T21:43:03+00:00"
}, },
{ {
"name": "hosteurope/password-generator", "name": "hosteurope/password-generator",
@ -537,105 +537,6 @@
}, },
"time": "2017-08-17T12:23:43+00:00" "time": "2017-08-17T12:23:43+00:00"
}, },
{
"name": "laminas/laminas-diactoros",
"version": "2.4.1",
"source": {
"type": "git",
"url": "https://github.com/laminas/laminas-diactoros.git",
"reference": "36ef09b73e884135d2059cc498c938e90821bb57"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/36ef09b73e884135d2059cc498c938e90821bb57",
"reference": "36ef09b73e884135d2059cc498c938e90821bb57",
"shasum": ""
},
"require": {
"laminas/laminas-zendframework-bridge": "^1.0",
"php": "^7.1",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.0"
},
"conflict": {
"phpspec/prophecy": "<1.9.0"
},
"provide": {
"psr/http-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0"
},
"replace": {
"zendframework/zend-diactoros": "^2.2.1"
},
"require-dev": {
"ext-curl": "*",
"ext-dom": "*",
"ext-gd": "*",
"ext-libxml": "*",
"http-interop/http-factory-tests": "^0.5.0",
"laminas/laminas-coding-standard": "~1.0.0",
"php-http/psr7-integration-tests": "^1.0",
"phpunit/phpunit": "^7.5.18"
},
"type": "library",
"extra": {
"laminas": {
"config-provider": "Laminas\\Diactoros\\ConfigProvider",
"module": "Laminas\\Diactoros"
}
},
"autoload": {
"files": [
"src/functions/create_uploaded_file.php",
"src/functions/marshal_headers_from_sapi.php",
"src/functions/marshal_method_from_sapi.php",
"src/functions/marshal_protocol_version_from_sapi.php",
"src/functions/marshal_uri_from_sapi.php",
"src/functions/normalize_server.php",
"src/functions/normalize_uploaded_files.php",
"src/functions/parse_cookie_header.php",
"src/functions/create_uploaded_file.legacy.php",
"src/functions/marshal_headers_from_sapi.legacy.php",
"src/functions/marshal_method_from_sapi.legacy.php",
"src/functions/marshal_protocol_version_from_sapi.legacy.php",
"src/functions/marshal_uri_from_sapi.legacy.php",
"src/functions/normalize_server.legacy.php",
"src/functions/normalize_uploaded_files.legacy.php",
"src/functions/parse_cookie_header.legacy.php"
],
"psr-4": {
"Laminas\\Diactoros\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "PSR HTTP Message implementations",
"homepage": "https://laminas.dev",
"keywords": [
"http",
"laminas",
"psr",
"psr-17",
"psr-7"
],
"support": {
"chat": "https://laminas.dev/chat",
"docs": "https://docs.laminas.dev/laminas-diactoros/",
"forum": "https://discourse.laminas.dev",
"issues": "https://github.com/laminas/laminas-diactoros/issues",
"rss": "https://github.com/laminas/laminas-diactoros/releases.atom",
"source": "https://github.com/laminas/laminas-diactoros"
},
"funding": [
{
"url": "https://funding.communitybridge.org/projects/laminas-project",
"type": "community_bridge"
}
],
"time": "2020-09-03T14:29:41+00:00"
},
{ {
"name": "laminas/laminas-httphandlerrunner", "name": "laminas/laminas-httphandlerrunner",
"version": "1.2.0", "version": "1.2.0",
@ -892,61 +793,6 @@
}, },
"time": "2020-09-15T07:28:23+00:00" "time": "2020-09-15T07:28:23+00:00"
}, },
{
"name": "psr/http-factory",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
"reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
"reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
"shasum": ""
},
"require": {
"php": ">=7.0.0",
"psr/http-message": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interfaces for PSR-7 HTTP message factories",
"keywords": [
"factory",
"http",
"message",
"psr",
"psr-17",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-factory/tree/master"
},
"time": "2019-04-30T12:38:16+00:00"
},
{ {
"name": "psr/http-message", "name": "psr/http-message",
"version": "1.0.1", "version": "1.0.1",
@ -1153,16 +999,16 @@
}, },
{ {
"name": "symfony/polyfill-intl-idn", "name": "symfony/polyfill-intl-idn",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git", "url": "https://github.com/symfony/polyfill-intl-idn.git",
"reference": "749045c69efb97c70d25d7463abba812e91f3a44" "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/749045c69efb97c70d25d7463abba812e91f3a44", "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/59a8d271f00dd0e4c2e518104cc7963f655a1aa8",
"reference": "749045c69efb97c70d25d7463abba812e91f3a44", "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1176,7 +1022,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1220,7 +1066,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1236,20 +1082,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-09-14T14:02:44+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-intl-normalizer", "name": "symfony/polyfill-intl-normalizer",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" "reference": "219aa369ceff116e673852dce47c3a41794c14bd"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd",
"reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", "reference": "219aa369ceff116e673852dce47c3a41794c14bd",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1261,7 +1107,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1304,7 +1150,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1320,20 +1166,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-02-19T12:13:01+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-php72", "name": "symfony/polyfill-php72",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php72.git", "url": "https://github.com/symfony/polyfill-php72.git",
"reference": "9a142215a36a3888e30d0a9eeea9766764e96976" "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2",
"reference": "9a142215a36a3888e30d0a9eeea9766764e96976", "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1342,7 +1188,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1380,7 +1226,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-php72/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1396,7 +1242,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-05-27T09:17:38+00:00" "time": "2022-05-24T11:49:31+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [

2
lib/Arsse.php

@ -7,7 +7,7 @@ declare(strict_types=1);
namespace JKingWeb\Arsse; namespace JKingWeb\Arsse;
class Arsse { class Arsse {
public const VERSION = "0.10.2"; public const VERSION = "0.10.3";
public const REQUIRED_EXTENSIONS = [ public const REQUIRED_EXTENSIONS = [
"intl", // as this extension is required to prepare formatted messages, its absence will throw a distinct English-only exception "intl", // as this extension is required to prepare formatted messages, its absence will throw a distinct English-only exception
"dom", "dom",

4
lib/Db/MySQL/ExceptionBuilder.php

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

27
lib/Misc/HTTP.php

@ -7,16 +7,41 @@ declare(strict_types=1);
namespace JKingWeb\Arsse\Misc; namespace JKingWeb\Arsse\Misc;
use Psr\Http\Message\MessageInterface; use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Psr7\Response;
class HTTP { class HTTP {
public static function matchType(MessageInterface $msg, string ...$type): bool { public static function matchType(MessageInterface $msg, string ...$type): bool {
$header = $msg->getHeaderLine("Content-Type") ?? ""; $header = $msg->getHeaderLine("Content-Type") ?? "";
foreach ($type as $t) { foreach ($type as $t) {
$pattern = "/^".preg_quote(trim($t), "/")."\s*($|;|,)/Di"; if (($t[0] ?? "") === "+") {
$pattern = "/^[^+;,\s]*".preg_quote(trim($t), "/")."\s*($|;|,)/Di";
} else {
$pattern = "/^".preg_quote(trim($t), "/")."\s*($|;|,)/Di";
}
if (preg_match($pattern, $header)) { if (preg_match($pattern, $header)) {
return true; return true;
} }
} }
return false; return false;
} }
public static function respEmpty(int $status, ?array $headers = []): ResponseInterface {
return new Response($status, $headers ?? []);
}
public static function respJson($body, int $status = 200, ?array $headers = []): ResponseInterface {
$headers = ($headers ?? []) + ['Content-Type' => "application/json"];
return new Response($status, $headers, json_encode($body, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE));
}
public static function respText(string $body, int $status = 200, ?array $headers = []): ResponseInterface {
$headers = ($headers ?? []) + ['Content-Type' => "text/plain; charset=UTF-8"];
return new Response($status, $headers, $body);
}
public static function respXml(string $body, int $status = 200, ?array $headers = []): ResponseInterface {
$headers = ($headers ?? []) + ['Content-Type' => "application/xml; charset=UTF-8"];
return new Response($status, $headers, $body);
}
} }

10
lib/REST.php

@ -7,11 +7,11 @@ declare(strict_types=1);
namespace JKingWeb\Arsse; namespace JKingWeb\Arsse;
use JKingWeb\Arsse\Misc\URL; use JKingWeb\Arsse\Misc\URL;
use JKingWeb\Arsse\Misc\HTTP;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\ServerRequestFactory; use GuzzleHttp\Psr7\ServerRequest;
use Laminas\Diactoros\Response\EmptyResponse;
class REST { class REST {
public const API_LIST = [ public const API_LIST = [
@ -84,7 +84,7 @@ class REST {
// ensure the require extensions are loaded // ensure the require extensions are loaded
Arsse::checkExtensions(...Arsse::REQUIRED_EXTENSIONS); Arsse::checkExtensions(...Arsse::REQUIRED_EXTENSIONS);
// create a request object if not provided // create a request object if not provided
$req = $req ?? ServerRequestFactory::fromGlobals(); $req = $req ?? ServerRequest::fromGlobals();
// find the API to handle // find the API to handle
[, $target, $class] = $this->apiMatch($req->getRequestTarget(), $this->apis); [, $target, $class] = $this->apiMatch($req->getRequestTarget(), $this->apis);
// authenticate the request pre-emptively // authenticate the request pre-emptively
@ -101,7 +101,7 @@ class REST {
$res = $drv->dispatch($req); $res = $drv->dispatch($req);
} }
} catch (REST\Exception501 $e) { } catch (REST\Exception501 $e) {
$res = new EmptyResponse(501); $res = HTTP::respEmpty(501);
} }
// modify the response so that it has all the required metadata // modify the response so that it has all the required metadata
return $this->normalizeResponse($res, $req); return $this->normalizeResponse($res, $req);
@ -180,7 +180,7 @@ class REST {
} }
// if the response is to a HEAD request, the body should be omitted // if the response is to a HEAD request, the body should be omitted
if ($req && $req->getMethod() === "HEAD") { if ($req && $req->getMethod() === "HEAD") {
$res = new EmptyResponse($res->getStatusCode(), $res->getHeaders()); $res = HTTP::respEmpty($res->getStatusCode(), $res->getHeaders());
} }
// if an Allow header field is present, normalize it // if an Allow header field is present, normalize it
if ($res->hasHeader("Allow")) { if ($res->hasHeader("Allow")) {

17
lib/REST/Fever/API.php

@ -10,13 +10,10 @@ use JKingWeb\Arsse\Arsse;
use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Context\Context;
use JKingWeb\Arsse\Misc\ValueInfo as V; use JKingWeb\Arsse\Misc\ValueInfo as V;
use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Misc\HTTP; use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Db\ExceptionInput;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse;
use Laminas\Diactoros\Response\XmlResponse;
use Laminas\Diactoros\Response\EmptyResponse;
class API extends \JKingWeb\Arsse\REST\AbstractHandler { class API extends \JKingWeb\Arsse\REST\AbstractHandler {
public const LEVEL = 3; public const LEVEL = 3;
@ -62,11 +59,11 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
$P = $this->normalizeInputPost($req->getParsedBody() ?? []); $P = $this->normalizeInputPost($req->getParsedBody() ?? []);
if (!isset($G['api'])) { if (!isset($G['api'])) {
// the original would have shown the Fever UI in the absence of the "api" parameter, but we'll return 404 // the original would have shown the Fever UI in the absence of the "api" parameter, but we'll return 404
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
switch ($req->getMethod()) { switch ($req->getMethod()) {
case "OPTIONS": case "OPTIONS":
return new EmptyResponse(204, [ return HTTP::respEmpty(204, [
'Allow' => "POST", 'Allow' => "POST",
'Accept' => implode(", ", self::ACCEPTED_TYPES), 'Accept' => implode(", ", self::ACCEPTED_TYPES),
]); ]);
@ -82,7 +79,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
$out['auth'] = 1; $out['auth'] = 1;
} elseif (Arsse::$conf->userHTTPAuthRequired || Arsse::$conf->userPreAuth || $req->getAttribute("authenticationFailed", false)) { } elseif (Arsse::$conf->userHTTPAuthRequired || Arsse::$conf->userPreAuth || $req->getAttribute("authenticationFailed", false)) {
// otherwise if HTTP authentication failed or is required, deny access at the HTTP level // otherwise if HTTP authentication failed or is required, deny access at the HTTP level
return new EmptyResponse(401); return HTTP::respEmpty(401);
} }
// produce a full response if authenticated or a basic response otherwise // produce a full response if authenticated or a basic response otherwise
if ($this->logIn(strtolower($P['api_key'] ?? ""))) { if ($this->logIn(strtolower($P['api_key'] ?? ""))) {
@ -93,7 +90,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
// return the result, possibly formatted as XML // return the result, possibly formatted as XML
return $this->formatResponse($out, ($G['api'] === "xml")); return $this->formatResponse($out, ($G['api'] === "xml"));
default: default:
return new EmptyResponse(405, ['Allow' => "OPTIONS,POST"]); return HTTP::respEmpty(405, ['Allow' => "OPTIONS,POST"]);
} }
} }
@ -182,9 +179,9 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
if ($xml) { if ($xml) {
$d = new \DOMDocument("1.0", "utf-8"); $d = new \DOMDocument("1.0", "utf-8");
$d->appendChild($this->makeXMLAssoc($data, $d->createElement("response"))); $d->appendChild($this->makeXMLAssoc($data, $d->createElement("response")));
return new XmlResponse($d->saveXML()); return HTTP::respXml($d->saveXML());
} else { } else {
return new JsonResponse($data, 200, [], \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE); return HTTP::respJson($data, 200, [], \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE);
} }
} }

19
lib/REST/Miniflux/ErrorResponse.php

@ -1,19 +0,0 @@
<?php
/** @license MIT
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse\REST\Miniflux;
use JKingWeb\Arsse\Arsse;
class ErrorResponse extends \Laminas\Diactoros\Response\JsonResponse {
public function __construct($data, int $status = 400, array $headers = [], int $encodingOptions = self::DEFAULT_JSON_FLAGS) {
assert(isset(Arsse::$lang) && Arsse::$lang instanceof \JKingWeb\Arsse\Lang, new \Exception("Language database must be initialized before use"));
$data = (array) $data;
$msg = array_shift($data);
$data = ["error_message" => Arsse::$lang->msg("API.Miniflux.Error.".$msg, $data)];
parent::__construct($data, $status, $headers, $encodingOptions);
}
}

11
lib/REST/Miniflux/Status.php

@ -6,10 +6,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\Arsse\REST\Miniflux; namespace JKingWeb\Arsse\REST\Miniflux;
use JKingWeb\Arsse\Misc\HTTP;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\EmptyResponse;
use Laminas\Diactoros\Response\TextResponse;
class Status extends \JKingWeb\Arsse\REST\AbstractHandler { class Status extends \JKingWeb\Arsse\REST\AbstractHandler {
public function __construct() { public function __construct() {
@ -18,13 +17,13 @@ class Status extends \JKingWeb\Arsse\REST\AbstractHandler {
public function dispatch(ServerRequestInterface $req): ResponseInterface { public function dispatch(ServerRequestInterface $req): ResponseInterface {
$target = parse_url($req->getRequestTarget())['path'] ?? ""; $target = parse_url($req->getRequestTarget())['path'] ?? "";
if (!in_array($target, ["/version", "/healthcheck"])) { if (!in_array($target, ["/version", "/healthcheck"])) {
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
$method = $req->getMethod(); $method = $req->getMethod();
if ($method === "OPTIONS") { if ($method === "OPTIONS") {
return new EmptyResponse(204, ['Allow' => "HEAD, GET"]); return HTTP::respEmpty(204, ['Allow' => "HEAD, GET"]);
} elseif ($method !== "GET") { } elseif ($method !== "GET") {
return new EmptyResponse(405, ['Allow' => "HEAD, GET"]); return HTTP::respEmpty(405, ['Allow' => "HEAD, GET"]);
} }
$out = ""; $out = "";
if ($target === "/version") { if ($target === "/version") {
@ -32,6 +31,6 @@ class Status extends \JKingWeb\Arsse\REST\AbstractHandler {
} elseif ($target === "/healthcheck") { } elseif ($target === "/healthcheck") {
$out = "OK"; $out = "OK";
} }
return new TextResponse($out); return HTTP::respText($out);
} }
} }

216
lib/REST/Miniflux/V1.php

@ -19,6 +19,7 @@ use JKingWeb\Arsse\ImportExport\OPML;
use JKingWeb\Arsse\ImportExport\Exception as ImportException; use JKingWeb\Arsse\ImportExport\Exception as ImportException;
use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\URL; use JKingWeb\Arsse\Misc\URL;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Misc\ValueInfo as V; use JKingWeb\Arsse\Misc\ValueInfo as V;
use JKingWeb\Arsse\REST\Exception; use JKingWeb\Arsse\REST\Exception;
use JKingWeb\Arsse\Rule\Rule; use JKingWeb\Arsse\Rule\Rule;
@ -26,10 +27,7 @@ use JKingWeb\Arsse\User\ExceptionConflict;
use JKingWeb\Arsse\User\Exception as UserException; use JKingWeb\Arsse\User\Exception as UserException;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\EmptyResponse; use GuzzleHttp\Psr7\Uri;
use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\TextResponse as GenericResponse;
use Laminas\Diactoros\Uri;
class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
public const VERSION = "2.0.28"; public const VERSION = "2.0.28";
@ -215,6 +213,14 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
public function __construct() { 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;
$msg = array_shift($data);
$data = ["error_message" => Arsse::$lang->msg("API.Miniflux.Error.".$msg, $data)];
return HTTP::respJson($data, $status, $headers);
}
protected function authenticate(ServerRequestInterface $req): bool { protected function authenticate(ServerRequestInterface $req): bool {
// first check any tokens; this is what Miniflux does // first check any tokens; this is what Miniflux does
if ($req->hasHeader("X-Auth-Token")) { if ($req->hasHeader("X-Auth-Token")) {
@ -247,7 +253,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} }
// try to authenticate // try to authenticate
if (!$this->authenticate($req)) { if (!$this->authenticate($req)) {
return new ErrorResponse("401", 401); return self::respError("401", 401);
} }
$func = $this->chooseCall($target, $method); $func = $this->chooseCall($target, $method);
if ($func instanceof ResponseInterface) { if ($func instanceof ResponseInterface) {
@ -256,7 +262,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
[$func, $reqAdmin, $reqPath, $reqBody, $reqQuery, $reqFields] = $func; [$func, $reqAdmin, $reqPath, $reqBody, $reqQuery, $reqFields] = $func;
} }
if ($reqAdmin && !$this->isAdmin()) { if ($reqAdmin && !$this->isAdmin()) {
return new ErrorResponse("403", 403); return self::respError("403", 403);
} }
$args = []; $args = [];
if ($reqPath) { if ($reqPath) {
@ -271,7 +277,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
$data = @json_decode($data, true); $data = @json_decode($data, true);
if (json_last_error() !== \JSON_ERROR_NONE) { if (json_last_error() !== \JSON_ERROR_NONE) {
// if the body could not be parsed as JSON, return "400 Bad Request" // if the body could not be parsed as JSON, return "400 Bad Request"
return new ErrorResponse(["InvalidBodyJSON", json_last_error_msg()], 400); return self::respError(["InvalidBodyJSON", json_last_error_msg()], 400);
} }
} else { } else {
$data = []; $data = [];
@ -295,10 +301,10 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
} catch (Exception $e) { } catch (Exception $e) {
// if there was a REST exception return 400 // if there was a REST exception return 400
return new EmptyResponse(400); return HTTP::respEmpty(400);
} catch (AbstractException $e) { } catch (AbstractException $e) {
// if there was any other Arsse exception return 500 // if there was any other Arsse exception return 500
return new EmptyResponse(500); return HTTP::respEmpty(500);
} }
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }
@ -317,11 +323,11 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
return self::CALLS[$url][$method]; return self::CALLS[$url][$method];
} else { } else {
// otherwise return 405 // otherwise return 405
return new EmptyResponse(405, ['Allow' => implode(", ", array_keys(self::CALLS[$url]))]); return HTTP::respEmpty(405, ['Allow' => implode(", ", array_keys(self::CALLS[$url]))]);
} }
} else { } else {
// if the path is not supported, return 404 // if the path is not supported, return 404
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
} }
@ -346,20 +352,20 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
if (!isset($body[$k])) { if (!isset($body[$k])) {
$body[$k] = null; $body[$k] = null;
} elseif (gettype($body[$k]) !== $t) { } elseif (gettype($body[$k]) !== $t) {
return new ErrorResponse(["InvalidInputType", 'field' => $k, 'expected' => $t, 'actual' => gettype($body[$k])], 422); return self::respError(["InvalidInputType", 'field' => $k, 'expected' => $t, 'actual' => gettype($body[$k])], 422);
} elseif ( } elseif (
(in_array($k, ["keeplist_rules", "blocklist_rules"]) && !Rule::validate($body[$k])) (in_array($k, ["keeplist_rules", "blocklist_rules"]) && !Rule::validate($body[$k]))
|| (in_array($k, ["url", "feed_url"]) && !URL::absolute($body[$k])) || (in_array($k, ["url", "feed_url"]) && !URL::absolute($body[$k]))
|| ($k === "category_id" && $body[$k] < 1) || ($k === "category_id" && $body[$k] < 1)
|| ($k === "status" && !in_array($body[$k], ["read", "unread", "removed"])) || ($k === "status" && !in_array($body[$k], ["read", "unread", "removed"]))
) { ) {
return new ErrorResponse(["InvalidInputValue", 'field' => $k], 422); return self::respError(["InvalidInputValue", 'field' => $k], 422);
} elseif ($k === "entry_ids") { } elseif ($k === "entry_ids") {
foreach ($body[$k] as $v) { foreach ($body[$k] as $v) {
if (gettype($v) !== "integer") { if (gettype($v) !== "integer") {
return new ErrorResponse(["InvalidInputType", 'field' => $k, 'expected' => "integer", 'actual' => gettype($v)], 422); return self::respError(["InvalidInputType", 'field' => $k, 'expected' => "integer", 'actual' => gettype($v)], 422);
} elseif ($v < 1) { } elseif ($v < 1) {
return new ErrorResponse(["InvalidInputValue", 'field' => $k], 422); return self::respError(["InvalidInputValue", 'field' => $k], 422);
} }
} }
} }
@ -371,16 +377,16 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
$body[$k] = null; $body[$k] = null;
} elseif ($k === "entry_sorting_direction") { } elseif ($k === "entry_sorting_direction") {
if (!in_array($body[$k], ["asc", "desc"])) { if (!in_array($body[$k], ["asc", "desc"])) {
return new ErrorResponse(["InvalidInputValue", 'field' => $k], 422); return self::respError(["InvalidInputValue", 'field' => $k], 422);
} }
} elseif (gettype($body[$k]) !== $t) { } elseif (gettype($body[$k]) !== $t) {
return new ErrorResponse(["InvalidInputType", 'field' => $k, 'expected' => $t, 'actual' => gettype($body[$k])], 422); return self::respError(["InvalidInputType", 'field' => $k, 'expected' => $t, 'actual' => gettype($body[$k])], 422);
} }
} }
// check for any missing required values // check for any missing required values
foreach ($req as $k) { foreach ($req as $k) {
if (!isset($body[$k]) || (is_array($body[$k]) && !$body[$k])) { if (!isset($body[$k]) || (is_array($body[$k]) && !$body[$k])) {
return new ErrorResponse(["MissingInputValue", 'field' => $k], 422); return self::respError(["MissingInputValue", 'field' => $k], 422);
} }
} }
return $body; return $body;
@ -409,7 +415,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
if ($seen[$k] && !$a) { if ($seen[$k] && !$a) {
// if the key has already been seen and it's not an array field, bail // if the key has already been seen and it's not an array field, bail
// NOTE: Miniflux itself simply ignores duplicates entirely // NOTE: Miniflux itself simply ignores duplicates entirely
return new ErrorResponse(["DuplicateInputValue", 'field' => $k], 400); return self::respError(["DuplicateInputValue", 'field' => $k], 400);
} }
$seen[$k] = true; $seen[$k] = true;
if ($k === "starred") { if ($k === "starred") {
@ -425,7 +431,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
$out[$k] = V::normalize($v, $t + V::M_STRICT, "unix"); $out[$k] = V::normalize($v, $t + V::M_STRICT, "unix");
} }
} catch (ExceptionType $e) { } catch (ExceptionType $e) {
return new ErrorResponse(["InvalidInputValue", 'field' => $k], 400); return self::respError(["InvalidInputValue", 'field' => $k], 400);
} }
// perform additional validation // perform additional validation
if ( if (
@ -435,7 +441,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
|| ($k === "order" && !in_array($v, ["id", "status", "published_at", "category_title", "category_id"])) || ($k === "order" && !in_array($v, ["id", "status", "published_at", "category_title", "category_id"]))
|| ($k === "status" && !in_array($v, ["read", "unread", "removed"])) || ($k === "status" && !in_array($v, ["read", "unread", "removed"]))
) { ) {
return new ErrorResponse(["InvalidInputValue", 'field' => $k], 400); return self::respError(["InvalidInputValue", 'field' => $k], 400);
} }
} }
return $out; return $out;
@ -451,13 +457,13 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
if (in_array("GET", $allowed)) { if (in_array("GET", $allowed)) {
array_unshift($allowed, "HEAD"); array_unshift($allowed, "HEAD");
} }
return new EmptyResponse(204, [ return HTTP::respEmpty(204, [
'Allow' => implode(", ", $allowed), 'Allow' => implode(", ", $allowed),
'Accept' => implode(", ", $url === "/import" ? self::ACCEPTED_TYPES_OPML : self::ACCEPTED_TYPES_JSON), 'Accept' => implode(", ", $url === "/import" ? self::ACCEPTED_TYPES_OPML : self::ACCEPTED_TYPES_JSON),
]); ]);
} else { } else {
// if the path is not supported, return 404 // if the path is not supported, return 404
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
} }
@ -527,40 +533,40 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
10507 => "Fetch401", 10507 => "Fetch401",
10521 => "Fetch404", 10521 => "Fetch404",
][$e->getCode()] ?? "FetchOther"; ][$e->getCode()] ?? "FetchOther";
return new ErrorResponse($msg, 502); return self::respError($msg, 502);
} }
$out = []; $out = [];
foreach ($list as $url) { foreach ($list as $url) {
// TODO: This needs to be refined once PicoFeed is replaced // TODO: This needs to be refined once PicoFeed is replaced
$out[] = ['title' => "Feed", 'type' => "rss", 'url' => $url]; $out[] = ['title' => "Feed", 'type' => "rss", 'url' => $url];
} }
return new Response($out); return HTTP::respJson($out);
} }
protected function getUsers(): ResponseInterface { protected function getUsers(): ResponseInterface {
$tr = Arsse::$user->begin(); $tr = Arsse::$user->begin();
return new Response($this->listUsers(Arsse::$user->list(), false)); return HTTP::respJson($this->listUsers(Arsse::$user->list(), false));
} }
protected function getUserById(array $path): ResponseInterface { protected function getUserById(array $path): ResponseInterface {
try { try {
return new Response($this->listUsers([$path[1]], true)[0] ?? new \stdClass); return HTTP::respJson($this->listUsers([$path[1]], true)[0] ?? new \stdClass);
} catch (UserException $e) { } catch (UserException $e) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
} }
protected function getUserByNum(array $path): ResponseInterface { protected function getUserByNum(array $path): ResponseInterface {
try { try {
$user = Arsse::$user->lookup((int) $path[1]); $user = Arsse::$user->lookup((int) $path[1]);
return new Response($this->listUsers([$user], true)[0] ?? new \stdClass); return HTTP::respJson($this->listUsers([$user], true)[0] ?? new \stdClass);
} catch (UserException $e) { } catch (UserException $e) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
} }
protected function getCurrentUser(): ResponseInterface { protected function getCurrentUser(): ResponseInterface {
return new Response($this->listUsers([Arsse::$user->id], false)[0] ?? new \stdClass); return HTTP::respJson($this->listUsers([Arsse::$user->id], false)[0] ?? new \stdClass);
} }
protected function createUser(array $data): ResponseInterface { protected function createUser(array $data): ResponseInterface {
@ -572,17 +578,17 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (UserException $e) { } catch (UserException $e) {
switch ($e->getCode()) { switch ($e->getCode()) {
case 10403: case 10403:
return new ErrorResponse(["DuplicateUser", 'user' => $data['username']], 409); return self::respError(["DuplicateUser", 'user' => $data['username']], 409);
case 10441: case 10441:
return new ErrorResponse(["InvalidInputValue", 'field' => "timezone"], 422); return self::respError(["InvalidInputValue", 'field' => "timezone"], 422);
case 10443: case 10443:
return new ErrorResponse(["InvalidInputValue", 'field' => "entries_per_page"], 422); return self::respError(["InvalidInputValue", 'field' => "entries_per_page"], 422);
case 10444: case 10444:
return new ErrorResponse(["InvalidInputValue", 'field' => "username"], 422); return self::respError(["InvalidInputValue", 'field' => "username"], 422);
} }
throw $e; // @codeCoverageIgnore throw $e; // @codeCoverageIgnore
} }
return new Response($out, 201); return HTTP::respJson($out, 201);
} }
protected function updateUserByNum(array $path, array $data): ResponseInterface { protected function updateUserByNum(array $path, array $data): ResponseInterface {
@ -591,16 +597,16 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
if (((int) $path[1]) === $user['num']) { if (((int) $path[1]) === $user['num']) {
if ($data['is_admin'] && !$user['admin']) { if ($data['is_admin'] && !$user['admin']) {
// non-admins should not be able to set themselves as admin // non-admins should not be able to set themselves as admin
return new ErrorResponse("InvalidElevation", 403); return self::respError("InvalidElevation", 403);
} }
$user = Arsse::$user->id; $user = Arsse::$user->id;
} elseif (!$user['admin']) { } elseif (!$user['admin']) {
return new ErrorResponse("403", 403); return self::respError("403", 403);
} else { } else {
try { try {
$user = Arsse::$user->lookup((int) $path[1]); $user = Arsse::$user->lookup((int) $path[1]);
} catch (ExceptionConflict $e) { } catch (ExceptionConflict $e) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
} }
// make any requested changes // make any requested changes
@ -618,26 +624,26 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (UserException $e) { } catch (UserException $e) {
switch ($e->getCode()) { switch ($e->getCode()) {
case 10403: case 10403:
return new ErrorResponse(["DuplicateUser", 'user' => $data['username']], 409); return self::respError(["DuplicateUser", 'user' => $data['username']], 409);
case 10441: case 10441:
return new ErrorResponse(["InvalidInputValue", 'field' => "timezone"], 422); return self::respError(["InvalidInputValue", 'field' => "timezone"], 422);
case 10443: case 10443:
return new ErrorResponse(["InvalidInputValue", 'field' => "entries_per_page"], 422); return self::respError(["InvalidInputValue", 'field' => "entries_per_page"], 422);
case 10444: case 10444:
return new ErrorResponse(["InvalidInputValue", 'field' => "username"], 422); return self::respError(["InvalidInputValue", 'field' => "username"], 422);
} }
throw $e; // @codeCoverageIgnore throw $e; // @codeCoverageIgnore
} }
return new Response($out, 201); return HTTP::respJson($out, 201);
} }
protected function deleteUserByNum(array $path): ResponseInterface { protected function deleteUserByNum(array $path): ResponseInterface {
try { try {
Arsse::$user->remove(Arsse::$user->lookup((int) $path[1])); Arsse::$user->remove(Arsse::$user->lookup((int) $path[1]));
} catch (ExceptionConflict $e) { } catch (ExceptionConflict $e) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
/** Returns a useful subset of user metadata /** Returns a useful subset of user metadata
@ -667,7 +673,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
// always add 1 to the ID since the root folder will always be 1 instead of 0. // always add 1 to the ID since the root folder will always be 1 instead of 0.
$out[] = ['id' => $f['id'] + 1, 'title' => $f['name'], 'user_id' => $meta['num']]; $out[] = ['id' => $f['id'] + 1, 'title' => $f['name'], 'user_id' => $meta['num']];
} }
return new Response($out); return HTTP::respJson($out);
} }
protected function createCategory(array $data): ResponseInterface { protected function createCategory(array $data): ResponseInterface {
@ -675,13 +681,13 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
$id = Arsse::$db->folderAdd(Arsse::$user->id, ['name' => (string) $data['title']]); $id = Arsse::$db->folderAdd(Arsse::$user->id, ['name' => (string) $data['title']]);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
if ($e->getCode() === 10236) { if ($e->getCode() === 10236) {
return new ErrorResponse(["DuplicateCategory", 'title' => $data['title']], 409); return self::respError(["DuplicateCategory", 'title' => $data['title']], 409);
} else { } else {
return new ErrorResponse(["InvalidCategory", 'title' => $data['title']], 422); return self::respError(["InvalidCategory", 'title' => $data['title']], 422);
} }
} }
$meta = Arsse::$user->propertiesGet(Arsse::$user->id, false); $meta = Arsse::$user->propertiesGet(Arsse::$user->id, false);
return new Response(['id' => $id + 1, 'title' => $data['title'], 'user_id' => $meta['num']], 201); return HTTP::respJson(['id' => $id + 1, 'title' => $data['title'], 'user_id' => $meta['num']], 201);
} }
protected function updateCategory(array $path, array $data): ResponseInterface { protected function updateCategory(array $path, array $data): ResponseInterface {
@ -700,15 +706,15 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} }
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
if ($e->getCode() === 10236) { if ($e->getCode() === 10236) {
return new ErrorResponse(["DuplicateCategory", 'title' => $title], 409); return self::respError(["DuplicateCategory", 'title' => $title], 409);
} elseif (in_array($e->getCode(), [10237, 10239])) { } elseif (in_array($e->getCode(), [10237, 10239])) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} else { } else {
return new ErrorResponse(["InvalidCategory", 'title' => $title], 422); return self::respError(["InvalidCategory", 'title' => $title], 422);
} }
} }
$meta = Arsse::$user->propertiesGet(Arsse::$user->id, false); $meta = Arsse::$user->propertiesGet(Arsse::$user->id, false);
return new Response(['id' => (int) $path[1], 'title' => $title, 'user_id' => $meta['num']], 201); return HTTP::respJson(['id' => (int) $path[1], 'title' => $title, 'user_id' => $meta['num']], 201);
} }
protected function deleteCategory(array $path): ResponseInterface { protected function deleteCategory(array $path): ResponseInterface {
@ -726,9 +732,9 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
$tr->commit(); $tr->commit();
} }
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function transformFeed(array $sub, int $uid, string $rootName, \DateTimeZone $tz): array { protected function transformFeed(array $sub, int $uid, string $rootName, \DateTimeZone $tz): array {
@ -772,7 +778,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
foreach (Arsse::$db->subscriptionList(Arsse::$user->id) as $r) { foreach (Arsse::$db->subscriptionList(Arsse::$user->id) as $r) {
$out[] = $this->transformFeed($r, $meta['num'], $meta['root'], $meta['tz']); $out[] = $this->transformFeed($r, $meta['num'], $meta['root'], $meta['tz']);
} }
return new Response($out); return HTTP::respJson($out);
} }
protected function getCategoryFeeds(array $path): ResponseInterface { protected function getCategoryFeeds(array $path): ResponseInterface {
@ -790,9 +796,9 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} }
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// the folder does not exist // the folder does not exist
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
return new Response($out); return HTTP::respJson($out);
} }
protected function getFeed(array $path): ResponseInterface { protected function getFeed(array $path): ResponseInterface {
@ -800,9 +806,9 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
$meta = $this->userMeta(Arsse::$user->id); $meta = $this->userMeta(Arsse::$user->id);
try { try {
$sub = Arsse::$db->subscriptionPropertiesGet(Arsse::$user->id, (int) $path[1]); $sub = Arsse::$db->subscriptionPropertiesGet(Arsse::$user->id, (int) $path[1]);
return new Response($this->transformFeed($sub, $meta['num'], $meta['root'], $meta['tz'])); return HTTP::respJson($this->transformFeed($sub, $meta['num'], $meta['root'], $meta['tz']));
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
} }
@ -825,16 +831,16 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
10521 => "Fetch404", 10521 => "Fetch404",
10522 => "FetchFormat", 10522 => "FetchFormat",
][$e->getCode()] ?? "FetchOther"; ][$e->getCode()] ?? "FetchOther";
return new ErrorResponse($msg, 502); return self::respError($msg, 502);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
switch ($e->getCode()) { switch ($e->getCode()) {
case 10235: case 10235:
return new ErrorResponse("MissingCategory", 422); return self::respError("MissingCategory", 422);
case 10236: case 10236:
return new ErrorResponse("DuplicateFeed", 409); return self::respError("DuplicateFeed", 409);
} }
} }
return new Response(['feed_id' => $id], 201); return HTTP::respJson(['feed_id' => $id], 201);
} }
protected function updateFeed(array $path, array $data): ResponseInterface { protected function updateFeed(array $path, array $data): ResponseInterface {
@ -853,11 +859,11 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
switch ($e->getCode()) { switch ($e->getCode()) {
case 10231: case 10231:
case 10232: case 10232:
return new ErrorResponse("InvalidTitle", 422); return self::respError("InvalidTitle", 422);
case 10235: case 10235:
return new ErrorResponse("MissingCategory", 422); return self::respError("MissingCategory", 422);
case 10239: case 10239:
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
} }
return $this->getFeed($path)->withStatus(201); return $this->getFeed($path)->withStatus(201);
@ -866,9 +872,9 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
protected function deleteFeed(array $path): ResponseInterface { protected function deleteFeed(array $path): ResponseInterface {
try { try {
Arsse::$db->subscriptionRemove(Arsse::$user->id, (int) $path[1]); Arsse::$db->subscriptionRemove(Arsse::$user->id, (int) $path[1]);
return new EmptyResponse(204); return HTTP::respEmpty(204);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
} }
@ -876,12 +882,12 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
try { try {
$icon = Arsse::$db->subscriptionIcon(Arsse::$user->id, (int) $path[1]); $icon = Arsse::$db->subscriptionIcon(Arsse::$user->id, (int) $path[1]);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
if (!$icon || !$icon['type'] || !$icon['data']) { if (!$icon || !$icon['type'] || !$icon['data']) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
return new Response([ return HTTP::respJson([
'id' => (int) $icon['id'], 'id' => (int) $icon['id'],
'data' => $icon['type'].";base64,".base64_encode($icon['data']), 'data' => $icon['type'].";base64,".base64_encode($icon['data']),
'mime_type' => $icon['type'], 'mime_type' => $icon['type'],
@ -1038,45 +1044,45 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
protected function getEntries(array $query): ResponseInterface { protected function getEntries(array $query): ResponseInterface {
try { try {
return new Response($this->listEntries($query, new Context)); return HTTP::respJson($this->listEntries($query, new Context));
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("MissingCategory", 400); return self::respError("MissingCategory", 400);
} }
} }
protected function getFeedEntries(array $path, array $query): ResponseInterface { protected function getFeedEntries(array $path, array $query): ResponseInterface {
$c = (new Context)->subscription((int) $path[1]); $c = (new Context)->subscription((int) $path[1]);
try { try {
return new Response($this->listEntries($query, $c)); return HTTP::respJson($this->listEntries($query, $c));
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// FIXME: this should differentiate between a missing feed and a missing category, but doesn't // FIXME: this should differentiate between a missing feed and a missing category, but doesn't
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
} }
protected function getCategoryEntries(array $path, array $query): ResponseInterface { protected function getCategoryEntries(array $path, array $query): ResponseInterface {
$query['category_id'] = (int) $path[1]; $query['category_id'] = (int) $path[1];
try { try {
return new Response($this->listEntries($query, new Context)); return HTTP::respJson($this->listEntries($query, new Context));
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
} }
protected function getEntry(array $path): ResponseInterface { protected function getEntry(array $path): ResponseInterface {
try { try {
return new Response($this->findEntry((int) $path[1])); return HTTP::respJson($this->findEntry((int) $path[1]));
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
} }
protected function getFeedEntry(array $path): ResponseInterface { protected function getFeedEntry(array $path): ResponseInterface {
$c = (new Context)->subscription((int) $path[1]); $c = (new Context)->subscription((int) $path[1]);
try { try {
return new Response($this->findEntry((int) $path[3], $c)); return HTTP::respJson($this->findEntry((int) $path[3], $c));
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
} }
@ -1088,9 +1094,9 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
$c->folder((int) $path[1] - 1); $c->folder((int) $path[1] - 1);
} }
try { try {
return new Response($this->findEntry((int) $path[3], $c)); return HTTP::respJson($this->findEntry((int) $path[3], $c));
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
} }
@ -1104,7 +1110,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} }
assert(isset($in), new \Exception("Unknown status specified")); assert(isset($in), new \Exception("Unknown status specified"));
Arsse::$db->articleMark(Arsse::$user->id, $in, (new Context)->articles($data['entry_ids'])); Arsse::$db->articleMark(Arsse::$user->id, $in, (new Context)->articles($data['entry_ids']));
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function massRead(Context $c): void { protected function massRead(Context $c): void {
@ -1115,19 +1121,19 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
// this function is restricted to the logged-in user // this function is restricted to the logged-in user
$user = Arsse::$user->propertiesGet(Arsse::$user->id, false); $user = Arsse::$user->propertiesGet(Arsse::$user->id, false);
if (((int) $path[1]) !== $user['num']) { if (((int) $path[1]) !== $user['num']) {
return new ErrorResponse("403", 403); return self::respError("403", 403);
} }
$this->massRead(new Context); $this->massRead(new Context);
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function markFeed(array $path): ResponseInterface { protected function markFeed(array $path): ResponseInterface {
try { try {
$this->massRead((new Context)->subscription((int) $path[1])); $this->massRead((new Context)->subscription((int) $path[1]));
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function markCategory(array $path): ResponseInterface { protected function markCategory(array $path): ResponseInterface {
@ -1142,9 +1148,9 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
try { try {
$this->massRead($c); $this->massRead($c);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function toggleEntryBookmark(array $path): ResponseInterface { protected function toggleEntryBookmark(array $path): ResponseInterface {
@ -1160,9 +1166,9 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} }
$tr->commit(); $tr->commit();
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function refreshFeed(array $path): ResponseInterface { protected function refreshFeed(array $path): ResponseInterface {
@ -1170,15 +1176,15 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
try { try {
Arsse::$db->subscriptionPropertiesGet(Arsse::$user->id, (int) $path[1]); Arsse::$db->subscriptionPropertiesGet(Arsse::$user->id, (int) $path[1]);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new ErrorResponse("404", 404); return self::respError("404", 404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function refreshAllFeeds(): ResponseInterface { protected function refreshAllFeeds(): ResponseInterface {
// NOTE: This is a no-op // NOTE: This is a no-op
// It could be implemented, but the need is considered low since we use a dynamic schedule always // It could be implemented, but the need is considered low since we use a dynamic schedule always
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function opmlImport(string $data): ResponseInterface { protected function opmlImport(string $data): ResponseInterface {
@ -1187,23 +1193,23 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (ImportException $e) { } catch (ImportException $e) {
switch ($e->getCode()) { switch ($e->getCode()) {
case 10611: case 10611:
return new ErrorResponse("InvalidBodyXML", 400); return self::respError("InvalidBodyXML", 400);
case 10612: case 10612:
return new ErrorResponse("InvalidBodyOPML", 422); return self::respError("InvalidBodyOPML", 422);
case 10613: case 10613:
return new ErrorResponse("InvalidImportCategory", 422); return self::respError("InvalidImportCategory", 422);
case 10614: case 10614:
return new ErrorResponse("DuplicateImportCategory", 422); return self::respError("DuplicateImportCategory", 422);
case 10615: case 10615:
return new ErrorResponse("InvalidImportLabel", 422); return self::respError("InvalidImportLabel", 422);
} }
} catch (FeedException $e) { } catch (FeedException $e) {
return new ErrorResponse(["FailedImportFeed", 'url' => $e->getParams()['url'], 'code' => $e->getCode()], 502); return self::respError(["FailedImportFeed", 'url' => $e->getParams()['url'], 'code' => $e->getCode()], 502);
} }
return new Response(['message' => Arsse::$lang->msg("API.Miniflux.ImportSuccess")]); return HTTP::respJson(['message' => Arsse::$lang->msg("API.Miniflux.ImportSuccess")]);
} }
protected function opmlExport(): ResponseInterface { protected function opmlExport(): ResponseInterface {
return new GenericResponse(Arsse::$obj->get(OPML::class)->export(Arsse::$user->id), 200, ['Content-Type' => "application/xml"]); return HTTP::respText(Arsse::$obj->get(OPML::class)->export(Arsse::$user->id), 200, ['Content-Type' => "application/xml"]);
} }
} }

142
lib/REST/NextcloudNews/V1_2.php

@ -17,8 +17,6 @@ use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\REST\Exception; use JKingWeb\Arsse\REST\Exception;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse;
class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
public const VERSION = "11.0.5"; public const VERSION = "11.0.5";
@ -86,19 +84,19 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
if ($req->getAttribute("authenticated", false)) { if ($req->getAttribute("authenticated", false)) {
Arsse::$user->id = $req->getAttribute("authenticatedUser"); Arsse::$user->id = $req->getAttribute("authenticatedUser");
} else { } else {
return new EmptyResponse(401); return HTTP::respEmpty(401);
} }
// normalize the input // normalize the input
$data = (string) $req->getBody(); $data = (string) $req->getBody();
if ($data) { if ($data) {
// if the entity body is not JSON according to content type, return "415 Unsupported Media Type" // if the entity body is not JSON according to content type, return "415 Unsupported Media Type"
if (!HTTP::matchType($req, "", self::ACCEPTED_TYPE)) { if (!HTTP::matchType($req, "", self::ACCEPTED_TYPE)) {
return new EmptyResponse(415, ['Accept' => self::ACCEPTED_TYPE]); return HTTP::respEmpty(415, ['Accept' => self::ACCEPTED_TYPE]);
} }
$data = @json_decode($data, true); $data = @json_decode($data, true);
if (json_last_error() !== \JSON_ERROR_NONE) { if (json_last_error() !== \JSON_ERROR_NONE) {
// if the body could not be parsed as JSON, return "400 Bad Request" // if the body could not be parsed as JSON, return "400 Bad Request"
return new EmptyResponse(400); return HTTP::respEmpty(400);
} }
} else { } else {
$data = []; $data = [];
@ -117,10 +115,10 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
} catch (Exception $e) { } catch (Exception $e) {
// if there was a REST exception return 400 // if there was a REST exception return 400
return new EmptyResponse(400); return HTTP::respEmpty(400);
} catch (AbstractException $e) { } catch (AbstractException $e) {
// if there was any other Arsse exception return 500 // if there was any other Arsse exception return 500
return new EmptyResponse(500); return HTTP::respEmpty(500);
} }
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }
@ -162,11 +160,11 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
return $this->paths[$url][$method]; return $this->paths[$url][$method];
} else { } else {
// otherwise return 405 // otherwise return 405
return new EmptyResponse(405, ['Allow' => implode(", ", array_keys($this->paths[$url]))]); return HTTP::respEmpty(405, ['Allow' => implode(", ", array_keys($this->paths[$url]))]);
} }
} else { } else {
// if the path is not supported, return 404 // if the path is not supported, return 404
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
} }
@ -268,13 +266,13 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
if (in_array("GET", $allowed)) { if (in_array("GET", $allowed)) {
array_unshift($allowed, "HEAD"); array_unshift($allowed, "HEAD");
} }
return new EmptyResponse(204, [ return HTTP::respEmpty(204, [
'Allow' => implode(",", $allowed), 'Allow' => implode(",", $allowed),
'Accept' => self::ACCEPTED_TYPE, 'Accept' => self::ACCEPTED_TYPE,
]); ]);
} else { } else {
// if the path is not supported, return 404 // if the path is not supported, return 404
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
} }
@ -284,7 +282,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
foreach (Arsse::$db->folderList(Arsse::$user->id, null, false) as $folder) { foreach (Arsse::$db->folderList(Arsse::$user->id, null, false) as $folder) {
$folders[] = $this->folderTranslate($folder); $folders[] = $this->folderTranslate($folder);
} }
return new Response(['folders' => $folders]); return HTTP::respJson(['folders' => $folders]);
} }
// create a folder // create a folder
@ -294,16 +292,16 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
switch ($e->getCode()) { switch ($e->getCode()) {
// folder already exists // folder already exists
case 10236: return new EmptyResponse(409); case 10236: return HTTP::respEmpty(409);
// folder name not acceptable // folder name not acceptable
case 10231: case 10231:
case 10232: return new EmptyResponse(422); case 10232: return HTTP::respEmpty(422);
// other errors related to input // other errors related to input
default: return new EmptyResponse(400); // @codeCoverageIgnore default: return HTTP::respEmpty(400); // @codeCoverageIgnore
} }
} }
$folder = $this->folderTranslate(Arsse::$db->folderPropertiesGet(Arsse::$user->id, $folder)); $folder = $this->folderTranslate(Arsse::$db->folderPropertiesGet(Arsse::$user->id, $folder));
return new Response(['folders' => [$folder]]); return HTTP::respJson(['folders' => [$folder]]);
} }
// delete a folder // delete a folder
@ -313,9 +311,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->folderRemove(Arsse::$user->id, (int) $url[1]); Arsse::$db->folderRemove(Arsse::$user->id, (int) $url[1]);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// folder does not exist // folder does not exist
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// rename a folder (also supports moving nesting folders, but this is not a feature of the API) // rename a folder (also supports moving nesting folders, but this is not a feature of the API)
@ -325,24 +323,24 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
switch ($e->getCode()) { switch ($e->getCode()) {
// folder does not exist // folder does not exist
case 10239: return new EmptyResponse(404); case 10239: return HTTP::respEmpty(404);
// folder already exists // folder already exists
case 10236: return new EmptyResponse(409); case 10236: return HTTP::respEmpty(409);
// folder name not acceptable // folder name not acceptable
case 10231: case 10231:
case 10232: return new EmptyResponse(422); case 10232: return HTTP::respEmpty(422);
// other errors related to input // other errors related to input
default: return new EmptyResponse(400); // @codeCoverageIgnore default: return HTTP::respEmpty(400); // @codeCoverageIgnore
} }
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// mark all articles associated with a folder as read // mark all articles associated with a folder as read
protected function folderMarkRead(array $url, array $data): ResponseInterface { protected function folderMarkRead(array $url, array $data): ResponseInterface {
if (!ValueInfo::id($data['newestItemId'])) { if (!ValueInfo::id($data['newestItemId'])) {
// if the item ID is invalid (i.e. not a positive integer), this is an error // if the item ID is invalid (i.e. not a positive integer), this is an error
return new EmptyResponse(422); return HTTP::respEmpty(422);
} }
// build the context // build the context
$c = (new Context)->hidden(false); $c = (new Context)->hidden(false);
@ -353,15 +351,15 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c); Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// folder does not exist // folder does not exist
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// return list of feeds which should be refreshed // return list of feeds which should be refreshed
protected function feedListStale(array $url, array $data): ResponseInterface { protected function feedListStale(array $url, array $data): ResponseInterface {
if (!$this->isAdmin()) { if (!$this->isAdmin()) {
return new EmptyResponse(403); return HTTP::respEmpty(403);
} }
// list stale feeds which should be checked for updates // list stale feeds which should be checked for updates
$feeds = Arsse::$db->feedListStale(); $feeds = Arsse::$db->feedListStale();
@ -370,27 +368,27 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
// since in our implementation feeds don't belong the users, the 'userId' field will always be an empty string // since in our implementation feeds don't belong the users, the 'userId' field will always be an empty string
$out[] = ['id' => (int) $feed, 'userId' => ""]; $out[] = ['id' => (int) $feed, 'userId' => ""];
} }
return new Response(['feeds' => $out]); return HTTP::respJson(['feeds' => $out]);
} }
// refresh a feed // refresh a feed
protected function feedUpdate(array $url, array $data): ResponseInterface { protected function feedUpdate(array $url, array $data): ResponseInterface {
if (!$this->isAdmin()) { if (!$this->isAdmin()) {
return new EmptyResponse(403); return HTTP::respEmpty(403);
} }
try { try {
Arsse::$db->feedUpdate($data['feedId']); Arsse::$db->feedUpdate($data['feedId']);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
switch ($e->getCode()) { switch ($e->getCode()) {
case 10239: // feed does not exist case 10239: // feed does not exist
return new EmptyResponse(404); return HTTP::respEmpty(404);
case 10237: // feed ID invalid case 10237: // feed ID invalid
return new EmptyResponse(422); return HTTP::respEmpty(422);
default: // other errors related to input default: // other errors related to input
return new EmptyResponse(400); // @codeCoverageIgnore return HTTP::respEmpty(400); // @codeCoverageIgnore
} }
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// add a new feed // add a new feed
@ -401,10 +399,10 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
$id = Arsse::$db->subscriptionAdd(Arsse::$user->id, (string) $data['url']); $id = Arsse::$db->subscriptionAdd(Arsse::$user->id, (string) $data['url']);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// feed already exists // feed already exists
return new EmptyResponse(409); return HTTP::respEmpty(409);
} catch (FeedException $e) { } catch (FeedException $e) {
// feed could not be retrieved // feed could not be retrieved
return new EmptyResponse(422); return HTTP::respEmpty(422);
} }
// if a folder was specified, move the feed to the correct folder; silently ignore errors // if a folder was specified, move the feed to the correct folder; silently ignore errors
if ($data['folderId']) { if ($data['folderId']) {
@ -422,7 +420,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
if ($newest) { if ($newest) {
$out['newestItemId'] = $newest; $out['newestItemId'] = $newest;
} }
return new Response($out); return HTTP::respJson($out);
} }
// return list of feeds for the logged-in user // return list of feeds for the logged-in user
@ -438,7 +436,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
if ($newest) { if ($newest) {
$out['newestItemId'] = $newest; $out['newestItemId'] = $newest;
} }
return new Response($out); return HTTP::respJson($out);
} }
// delete a feed // delete a feed
@ -447,9 +445,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->subscriptionRemove(Arsse::$user->id, (int) $url[1]); Arsse::$db->subscriptionRemove(Arsse::$user->id, (int) $url[1]);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// feed does not exist // feed does not exist
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// rename a feed // rename a feed
@ -459,22 +457,22 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
switch ($e->getCode()) { switch ($e->getCode()) {
// subscription does not exist // subscription does not exist
case 10239: return new EmptyResponse(404); case 10239: return HTTP::respEmpty(404);
// name is invalid // name is invalid
case 10231: case 10231:
case 10232: return new EmptyResponse(422); case 10232: return HTTP::respEmpty(422);
// other errors related to input // other errors related to input
default: return new EmptyResponse(400); // @codeCoverageIgnore default: return HTTP::respEmpty(400); // @codeCoverageIgnore
} }
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// move a feed to a folder // move a feed to a folder
protected function subscriptionMove(array $url, array $data): ResponseInterface { protected function subscriptionMove(array $url, array $data): ResponseInterface {
// if no folder is specified this is an error // if no folder is specified this is an error
if (!isset($data['folderId'])) { if (!isset($data['folderId'])) {
return new EmptyResponse(422); return HTTP::respEmpty(422);
} }
// perform the move // perform the move
try { try {
@ -482,22 +480,22 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
switch ($e->getCode()) { switch ($e->getCode()) {
case 10239: // subscription does not exist case 10239: // subscription does not exist
return new EmptyResponse(404); return HTTP::respEmpty(404);
case 10235: // folder does not exist case 10235: // folder does not exist
case 10237: // folder ID is invalid case 10237: // folder ID is invalid
return new EmptyResponse(422); return HTTP::respEmpty(422);
default: // other errors related to input default: // other errors related to input
return new EmptyResponse(400); // @codeCoverageIgnore return HTTP::respEmpty(400); // @codeCoverageIgnore
} }
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// mark all articles associated with a subscription as read // mark all articles associated with a subscription as read
protected function subscriptionMarkRead(array $url, array $data): ResponseInterface { protected function subscriptionMarkRead(array $url, array $data): ResponseInterface {
if (!ValueInfo::id($data['newestItemId'])) { if (!ValueInfo::id($data['newestItemId'])) {
// if the item ID is invalid (i.e. not a positive integer), this is an error // if the item ID is invalid (i.e. not a positive integer), this is an error
return new EmptyResponse(422); return HTTP::respEmpty(422);
} }
// build the context // build the context
$c = (new Context)->hidden(false); $c = (new Context)->hidden(false);
@ -508,9 +506,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c); Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// subscription does not exist // subscription does not exist
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// list articles and their properties // list articles and their properties
@ -579,28 +577,28 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
], [$reverse ? "edition desc" : "edition"]); ], [$reverse ? "edition desc" : "edition"]);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// ID of subscription or folder is not valid // ID of subscription or folder is not valid
return new EmptyResponse(422); return HTTP::respEmpty(422);
} }
$out = []; $out = [];
foreach ($items as $item) { foreach ($items as $item) {
$out[] = $this->articleTranslate($item); $out[] = $this->articleTranslate($item);
} }
$out = ['items' => $out]; $out = ['items' => $out];
return new Response($out); return HTTP::respJson($out);
} }
// mark all articles as read // mark all articles as read
protected function articleMarkReadAll(array $url, array $data): ResponseInterface { protected function articleMarkReadAll(array $url, array $data): ResponseInterface {
if (!ValueInfo::id($data['newestItemId'])) { if (!ValueInfo::id($data['newestItemId'])) {
// if the item ID is invalid (i.e. not a positive integer), this is an error // if the item ID is invalid (i.e. not a positive integer), this is an error
return new EmptyResponse(422); return HTTP::respEmpty(422);
} }
// build the context // build the context
$c = (new Context)->hidden(false); $c = (new Context)->hidden(false);
$c->editionRange(null, (int) $data['newestItemId']); $c->editionRange(null, (int) $data['newestItemId']);
// perform the operation // perform the operation
Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c); Arsse::$db->articleMark(Arsse::$user->id, ['read' => true], $c);
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// mark a single article as read // mark a single article as read
@ -614,9 +612,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->articleMark(Arsse::$user->id, ['read' => $set], $c); Arsse::$db->articleMark(Arsse::$user->id, ['read' => $set], $c);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// ID is not valid // ID is not valid
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// mark a single article as read // mark a single article as read
@ -630,9 +628,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->articleMark(Arsse::$user->id, ['starred' => $set], $c); Arsse::$db->articleMark(Arsse::$user->id, ['starred' => $set], $c);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
// ID is not valid // ID is not valid
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// mark an array of articles as read // mark an array of articles as read
@ -646,7 +644,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->articleMark(Arsse::$user->id, ['read' => $set], $c); Arsse::$db->articleMark(Arsse::$user->id, ['read' => $set], $c);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// mark an array of articles as starred // mark an array of articles as starred
@ -660,11 +658,11 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$db->articleMark(Arsse::$user->id, ['starred' => $set], $c); Arsse::$db->articleMark(Arsse::$user->id, ['starred' => $set], $c);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
} }
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function userStatus(array $url, array $data): ResponseInterface { protected function userStatus(array $url, array $data): ResponseInterface {
return new Response([ return HTTP::respJson([
'userId' => (string) Arsse::$user->id, 'userId' => (string) Arsse::$user->id,
'displayName' => (string) Arsse::$user->id, 'displayName' => (string) Arsse::$user->id,
'lastLoginTimestamp' => time(), 'lastLoginTimestamp' => time(),
@ -674,30 +672,30 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
protected function cleanupBefore(array $url, array $data): ResponseInterface { protected function cleanupBefore(array $url, array $data): ResponseInterface {
if (!$this->isAdmin()) { if (!$this->isAdmin()) {
return new EmptyResponse(403); return HTTP::respEmpty(403);
} }
Service::cleanupPre(); Service::cleanupPre();
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
protected function cleanupAfter(array $url, array $data): ResponseInterface { protected function cleanupAfter(array $url, array $data): ResponseInterface {
if (!$this->isAdmin()) { if (!$this->isAdmin()) {
return new EmptyResponse(403); return HTTP::respEmpty(403);
} }
Service::cleanupPost(); Service::cleanupPost();
return new EmptyResponse(204); return HTTP::respEmpty(204);
} }
// return the server version // return the server version
protected function serverVersion(array $url, array $data): ResponseInterface { protected function serverVersion(array $url, array $data): ResponseInterface {
return new Response([ return HTTP::respJson([
'version' => self::VERSION, 'version' => self::VERSION,
'arsse_version' => Arsse::VERSION, 'arsse_version' => Arsse::VERSION,
]); ]);
} }
protected function serverStatus(array $url, array $data): ResponseInterface { protected function serverStatus(array $url, array $data): ResponseInterface {
return new Response([ return HTTP::respJson([
'version' => self::VERSION, 'version' => self::VERSION,
'arsse_version' => Arsse::VERSION, 'arsse_version' => Arsse::VERSION,
'warnings' => [ 'warnings' => [

11
lib/REST/NextcloudNews/Versions.php

@ -6,10 +6,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\Arsse\REST\NextcloudNews; namespace JKingWeb\Arsse\REST\NextcloudNews;
use JKingWeb\Arsse\Misc\HTTP;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse;
class Versions implements \JKingWeb\Arsse\REST\Handler { class Versions implements \JKingWeb\Arsse\REST\Handler {
public function __construct() { public function __construct() {
@ -18,12 +17,12 @@ class Versions implements \JKingWeb\Arsse\REST\Handler {
public function dispatch(ServerRequestInterface $req): ResponseInterface { public function dispatch(ServerRequestInterface $req): ResponseInterface {
if (!preg_match("<^/?$>D", $req->getRequestTarget())) { if (!preg_match("<^/?$>D", $req->getRequestTarget())) {
// if the request path is more than an empty string or a slash, the client is probably trying a version we don't support // if the request path is more than an empty string or a slash, the client is probably trying a version we don't support
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
switch ($req->getMethod()) { switch ($req->getMethod()) {
case "OPTIONS": case "OPTIONS":
// if the request method is OPTIONS, respond accordingly // if the request method is OPTIONS, respond accordingly
return new EmptyResponse(204, ['Allow' => "HEAD,GET"]); return HTTP::respEmpty(204, ['Allow' => "HEAD,GET"]);
case "GET": case "GET":
// otherwise return the supported versions // otherwise return the supported versions
$out = [ $out = [
@ -31,10 +30,10 @@ class Versions implements \JKingWeb\Arsse\REST\Handler {
'v1-2', 'v1-2',
], ],
]; ];
return new Response($out); return HTTP::respJson($out);
default: default:
// if any other method was used, this is an error // if any other method was used, this is an error
return new EmptyResponse(405, ['Allow' => "HEAD,GET"]); return HTTP::respEmpty(405, ['Allow' => "HEAD,GET"]);
} }
} }
} }

21
lib/REST/TinyTinyRSS/API.php

@ -12,6 +12,7 @@ use JKingWeb\Arsse\Service;
use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Context\Context;
use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Misc\ValueInfo as V; use JKingWeb\Arsse\Misc\ValueInfo as V;
use JKingWeb\Arsse\AbstractException; use JKingWeb\Arsse\AbstractException;
use JKingWeb\Arsse\ExceptionType; use JKingWeb\Arsse\ExceptionType;
@ -20,8 +21,6 @@ use JKingWeb\Arsse\Db\ResultEmpty;
use JKingWeb\Arsse\Feed\Exception as FeedException; use JKingWeb\Arsse\Feed\Exception as FeedException;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse;
class API extends \JKingWeb\Arsse\REST\AbstractHandler { class API extends \JKingWeb\Arsse\REST\AbstractHandler {
public const LEVEL = 15; // emulated API level public const LEVEL = 15; // emulated API level
@ -96,11 +95,11 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
public function dispatch(ServerRequestInterface $req): ResponseInterface { public function dispatch(ServerRequestInterface $req): ResponseInterface {
if (!preg_match("<^(?:/(?:index\.php)?)?$>D", $req->getRequestTarget())) { if (!preg_match("<^(?:/(?:index\.php)?)?$>D", $req->getRequestTarget())) {
// reject paths other than the index // reject paths other than the index
return new EmptyResponse(404); return HTTP::respEmpty(404);
} }
if ($req->getMethod() === "OPTIONS") { if ($req->getMethod() === "OPTIONS") {
// respond to OPTIONS rquests; the response is a fib, as we technically accept any type or method // respond to OPTIONS rquests; the response is a fib, as we technically accept any type or method
return new EmptyResponse(204, [ return HTTP::respEmpty(204, [
'Allow' => "POST", 'Allow' => "POST",
'Accept' => implode(", ", self::ACCEPTED_TYPES), 'Accept' => implode(", ", self::ACCEPTED_TYPES),
]); ]);
@ -110,7 +109,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
// only JSON entities are allowed, but Content-Type is ignored, as is request method // only JSON entities are allowed, but Content-Type is ignored, as is request method
$data = @json_decode($data, true); $data = @json_decode($data, true);
if (json_last_error() !== \JSON_ERROR_NONE || !is_array($data)) { if (json_last_error() !== \JSON_ERROR_NONE || !is_array($data)) {
return new Response(self::FATAL_ERR); return HTTP::respJson(self::FATAL_ERR);
} }
try { try {
// normalize input // normalize input
@ -125,7 +124,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$user->id = $req->getAttribute("authenticatedUser"); Arsse::$user->id = $req->getAttribute("authenticatedUser");
} elseif (Arsse::$conf->userHTTPAuthRequired || Arsse::$conf->userPreAuth || $req->getAttribute("authenticationFailed", false)) { } elseif (Arsse::$conf->userHTTPAuthRequired || Arsse::$conf->userPreAuth || $req->getAttribute("authenticationFailed", false)) {
// otherwise if HTTP authentication failed or is required, deny access at the HTTP level // otherwise if HTTP authentication failed or is required, deny access at the HTTP level
return new EmptyResponse(401); return HTTP::respEmpty(401);
} }
if (strtolower((string) $data['op']) !== "login") { if (strtolower((string) $data['op']) !== "login") {
// unless logging in, a session identifier is required // unless logging in, a session identifier is required
@ -136,23 +135,23 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
// TT-RSS operations are case-insensitive by dint of PHP method names being case-insensitive; this will only trigger if the method really doesn't exist // TT-RSS operations are case-insensitive by dint of PHP method names being case-insensitive; this will only trigger if the method really doesn't exist
throw new Exception("UNKNOWN_METHOD", ['method' => $data['op']]); throw new Exception("UNKNOWN_METHOD", ['method' => $data['op']]);
} }
return new Response([ return HTTP::respJson([
'seq' => $data['seq'], 'seq' => $data['seq'],
'status' => 0, 'status' => 0,
'content' => $this->$method($data), 'content' => $this->$method($data),
]); ]);
} catch (Exception $e) { } catch (Exception $e) {
return new Response([ return HTTP::respJson([
'seq' => $data['seq'], 'seq' => $data['seq'],
'status' => 1, 'status' => 1,
'content' => $e->getData(), 'content' => $e->getData(),
]); ]);
} catch (AbstractException $e) { } catch (AbstractException $e) {
return new EmptyResponse(500); return HTTP::respEmpty(500);
} }
} else { } else {
// absence of a request body indicates an error // absence of a request body indicates an error
return new Response(self::FATAL_ERR); return HTTP::respJson(self::FATAL_ERR);
} }
} }
@ -1000,7 +999,7 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
switch ($e->getCode()) { switch ($e->getCode()) {
case 10236: // label already exists case 10236: // label already exists
// retrieve the ID of the existing label; duplicating a label silently returns the existing one // 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 default: // other errors related to input
throw new Exception("INCORRECT_USAGE"); throw new Exception("INCORRECT_USAGE");
} }

14
lib/REST/TinyTinyRSS/Icon.php

@ -7,10 +7,10 @@ declare(strict_types=1);
namespace JKingWeb\Arsse\REST\TinyTinyRSS; namespace JKingWeb\Arsse\REST\TinyTinyRSS;
use JKingWeb\Arsse\Arsse; use JKingWeb\Arsse\Arsse;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\EmptyResponse as Response;
class Icon extends \JKingWeb\Arsse\REST\AbstractHandler { class Icon extends \JKingWeb\Arsse\REST\AbstractHandler {
public function __construct() { public function __construct() {
@ -22,25 +22,25 @@ class Icon extends \JKingWeb\Arsse\REST\AbstractHandler {
Arsse::$user->id = $req->getAttribute("authenticatedUser"); Arsse::$user->id = $req->getAttribute("authenticatedUser");
} elseif ($req->getAttribute("authenticationFailed", false) || Arsse::$conf->userHTTPAuthRequired) { } elseif ($req->getAttribute("authenticationFailed", false) || Arsse::$conf->userHTTPAuthRequired) {
// otherwise if HTTP authentication failed or did not occur when it is required, deny access at the HTTP level // otherwise if HTTP authentication failed or did not occur when it is required, deny access at the HTTP level
return new Response(401); return HTTP::respEmpty(401);
} }
if ($req->getMethod() !== "GET") { if ($req->getMethod() !== "GET") {
// only GET requests are allowed // only GET requests are allowed
return new Response(405, ['Allow' => "GET"]); return HTTP::respEmpty(405, ['Allow' => "GET"]);
} elseif (!preg_match("<^(\d+)\.ico$>D", $req->getRequestTarget(), $match) || !((int) $match[1])) { } elseif (!preg_match("<^(\d+)\.ico$>D", $req->getRequestTarget(), $match) || !((int) $match[1])) {
return new Response(404); return HTTP::respEmpty(404);
} }
try { try {
$url = Arsse::$db->subscriptionIcon(Arsse::$user->id ?? null, (int) $match[1], false)['url'] ?? null; $url = Arsse::$db->subscriptionIcon(Arsse::$user->id ?? null, (int) $match[1], false)['url'] ?? null;
if (!$url) { if (!$url) {
return new Response(404); return HTTP::respEmpty(404);
} }
if (($pos = strpos($url, "\r")) !== false || ($pos = strpos($url, "\n")) !== false) { if (($pos = strpos($url, "\r")) !== false || ($pos = strpos($url, "\n")) !== false) {
$url = substr($url, 0, $pos); $url = substr($url, 0, $pos);
} }
return new Response(301, ['Location' => $url]); return HTTP::respEmpty(301, ['Location' => $url]);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
return new Response(404); return HTTP::respEmpty(404);
} }
} }
} }

2
locale/en.php

@ -34,7 +34,7 @@ return [
'API.Miniflux.Error.InvalidTitle' => 'Invalid feed title', 'API.Miniflux.Error.InvalidTitle' => 'Invalid feed title',
'API.Miniflux.Error.InvalidImportCategory' => 'Payload contains an invalid category name', 'API.Miniflux.Error.InvalidImportCategory' => 'Payload contains an invalid category name',
'API.Miniflux.Error.DuplicateImportCategory' => 'Payload contains the same category name twice', 'API.Miniflux.Error.DuplicateImportCategory' => 'Payload contains the same category name twice',
'API.Miniflux.Error.FailedImportFeed' => 'Unable to import feed at URL "{url}" (code {code}', 'API.Miniflux.Error.FailedImportFeed' => 'Unable to import feed at URL "{url}" (code {code})',
'API.Miniflux.Error.InvalidImportLabel' => 'Payload contains an invalid label name', 'API.Miniflux.Error.InvalidImportLabel' => 'Payload contains an invalid label name',
'API.TTRSS.Category.Uncategorized' => 'Uncategorized', 'API.TTRSS.Category.Uncategorized' => 'Uncategorized',

28
tests/cases/Database/SeriesArticle.php

@ -19,7 +19,7 @@ trait SeriesArticle {
$this->data = [ $this->data = [
'arsse_users' => [ 'arsse_users' => [
'columns' => ["id", "password", "num"], 'columns' => ["id", "password", "num"],
'rows' => [ 'rows' => [
["jane.doe@example.com", "", 1], ["jane.doe@example.com", "", 1],
["john.doe@example.com", "", 2], ["john.doe@example.com", "", 2],
["john.doe@example.org", "", 3], ["john.doe@example.org", "", 3],
@ -29,7 +29,7 @@ trait SeriesArticle {
], ],
'arsse_feeds' => [ 'arsse_feeds' => [
'columns' => ["id", "url", "title"], 'columns' => ["id", "url", "title"],
'rows' => [ 'rows' => [
[1,"http://example.com/1", "Feed 1"], [1,"http://example.com/1", "Feed 1"],
[2,"http://example.com/2", "Feed 2"], [2,"http://example.com/2", "Feed 2"],
[3,"http://example.com/3", "Feed 3"], [3,"http://example.com/3", "Feed 3"],
@ -47,7 +47,7 @@ trait SeriesArticle {
], ],
'arsse_folders' => [ 'arsse_folders' => [
'columns' => ["id", "owner", "parent", "name"], 'columns' => ["id", "owner", "parent", "name"],
'rows' => [ 'rows' => [
[1, "john.doe@example.com", null, "Technology"], [1, "john.doe@example.com", null, "Technology"],
[2, "john.doe@example.com", 1, "Software"], [2, "john.doe@example.com", 1, "Software"],
[3, "john.doe@example.com", 1, "Rocketry"], [3, "john.doe@example.com", 1, "Rocketry"],
@ -61,7 +61,7 @@ trait SeriesArticle {
], ],
'arsse_tags' => [ 'arsse_tags' => [
'columns' => ["id", "owner", "name"], 'columns' => ["id", "owner", "name"],
'rows' => [ 'rows' => [
[1, "john.doe@example.com", "Technology"], [1, "john.doe@example.com", "Technology"],
[2, "john.doe@example.com", "Software"], [2, "john.doe@example.com", "Software"],
[3, "john.doe@example.com", "Rocketry"], [3, "john.doe@example.com", "Rocketry"],
@ -74,7 +74,7 @@ trait SeriesArticle {
], ],
'arsse_subscriptions' => [ 'arsse_subscriptions' => [
'columns' => ["id", "owner", "feed", "folder", "title", "scrape"], 'columns' => ["id", "owner", "feed", "folder", "title", "scrape"],
'rows' => [ 'rows' => [
[1, "john.doe@example.com",1, null,"Subscription 1", 0], [1, "john.doe@example.com",1, null,"Subscription 1", 0],
[2, "john.doe@example.com",2, null,null, 0], [2, "john.doe@example.com",2, null,null, 0],
[3, "john.doe@example.com",3, 1,"Subscription 3", 0], [3, "john.doe@example.com",3, 1,"Subscription 3", 0],
@ -94,7 +94,7 @@ trait SeriesArticle {
], ],
'arsse_tag_members' => [ 'arsse_tag_members' => [
'columns' => ["tag", "subscription", "assigned"], 'columns' => ["tag", "subscription", "assigned"],
'rows' => [ 'rows' => [
[1,3,1], [1,3,1],
[1,4,1], [1,4,1],
[2,4,1], [2,4,1],
@ -109,8 +109,8 @@ trait SeriesArticle {
], ],
'arsse_articles' => [ 'arsse_articles' => [
'columns' => [ 'columns' => [
"id", "feed", "url", "title", "author", "published", "edited", "content", "guid", "id", "feed", "url", "title", "author", "published", "edited", "content", "guid",
"url_title_hash", "url_content_hash", "title_content_hash", "modified", "content_scraped" "url_title_hash", "url_content_hash", "title_content_hash", "modified", "content_scraped",
], ],
'rows' => [ 'rows' => [
[1,1,null,"Title one", null,null,null,"First article", null,"","","","2000-01-01T00:00:00Z",null], [1,1,null,"Title one", null,null,null,"First article", null,"","","","2000-01-01T00:00:00Z",null],
@ -142,7 +142,7 @@ trait SeriesArticle {
], ],
'arsse_enclosures' => [ 'arsse_enclosures' => [
'columns' => ["article", "url", "type"], 'columns' => ["article", "url", "type"],
'rows' => [ 'rows' => [
[102,"http://example.com/text","text/plain"], [102,"http://example.com/text","text/plain"],
[103,"http://example.com/video","video/webm"], [103,"http://example.com/video","video/webm"],
[104,"http://example.com/image","image/svg+xml"], [104,"http://example.com/image","image/svg+xml"],
@ -152,7 +152,7 @@ trait SeriesArticle {
], ],
'arsse_editions' => [ 'arsse_editions' => [
'columns' => ["id", "article"], 'columns' => ["id", "article"],
'rows' => [ 'rows' => [
[1,1], [1,1],
[2,2], [2,2],
[3,3], [3,3],
@ -188,7 +188,7 @@ trait SeriesArticle {
], ],
'arsse_marks' => [ 'arsse_marks' => [
'columns' => ["subscription", "article", "read", "starred", "modified", "note", "hidden"], 'columns' => ["subscription", "article", "read", "starred", "modified", "note", "hidden"],
'rows' => [ 'rows' => [
[1, 1,1,1,'2000-01-01 00:00:00','',0], [1, 1,1,1,'2000-01-01 00:00:00','',0],
[5, 19,1,0,'2016-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], [5, 20,0,1,'2005-01-01 00:00:00','',0],
@ -209,7 +209,7 @@ trait SeriesArticle {
], ],
'arsse_categories' => [ // author-supplied categories 'arsse_categories' => [ // author-supplied categories
'columns' => ["article", "name"], 'columns' => ["article", "name"],
'rows' => [ 'rows' => [
[19,"Fascinating"], [19,"Fascinating"],
[19,"Logical"], [19,"Logical"],
[20,"Interesting"], [20,"Interesting"],
@ -218,7 +218,7 @@ trait SeriesArticle {
], ],
'arsse_labels' => [ // labels applied to articles 'arsse_labels' => [ // labels applied to articles
'columns' => ["id", "owner", "name"], 'columns' => ["id", "owner", "name"],
'rows' => [ 'rows' => [
[1,"john.doe@example.com","Interesting"], [1,"john.doe@example.com","Interesting"],
[2,"john.doe@example.com","Fascinating"], [2,"john.doe@example.com","Fascinating"],
[3,"jane.doe@example.com","Boring"], [3,"jane.doe@example.com","Boring"],
@ -227,7 +227,7 @@ trait SeriesArticle {
], ],
'arsse_label_members' => [ 'arsse_label_members' => [
'columns' => ["label", "article", "subscription", "assigned", "modified"], 'columns' => ["label", "article", "subscription", "assigned", "modified"],
'rows' => [ 'rows' => [
[1, 1,1,1,'2000-01-01 00:00:00'], [1, 1,1,1,'2000-01-01 00:00:00'],
[2, 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'], [1,19,5,1,'2000-01-01 00:00:00'],

18
tests/cases/Database/SeriesCleanup.php

@ -28,14 +28,14 @@ trait SeriesCleanup {
$this->data = [ $this->data = [
'arsse_users' => [ 'arsse_users' => [
'columns' => ["id", "password", "num"], 'columns' => ["id", "password", "num"],
'rows' => [ 'rows' => [
["jane.doe@example.com", "",1], ["jane.doe@example.com", "",1],
["john.doe@example.com", "",2], ["john.doe@example.com", "",2],
], ],
], ],
'arsse_sessions' => [ 'arsse_sessions' => [
'columns' => ["id", "created", "expires", "user"], 'columns' => ["id", "created", "expires", "user"],
'rows' => [ 'rows' => [
["a", $nowish, $faroff, "jane.doe@example.com"], // not expired and recently created, thus kept ["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 ["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 ["c", $daysago, $soon, "jane.doe@example.com"], // created more than a day ago, thus deleted
@ -45,7 +45,7 @@ trait SeriesCleanup {
], ],
'arsse_tokens' => [ 'arsse_tokens' => [
'columns' => ["id", "class", "user", "expires"], 'columns' => ["id", "class", "user", "expires"],
'rows' => [ 'rows' => [
["80fa94c1a11f11e78667001e673b2560", "fever.login", "jane.doe@example.com", $faroff], ["80fa94c1a11f11e78667001e673b2560", "fever.login", "jane.doe@example.com", $faroff],
["27c6de8da13311e78667001e673b2560", "fever.login", "jane.doe@example.com", $weeksago], // expired ["27c6de8da13311e78667001e673b2560", "fever.login", "jane.doe@example.com", $weeksago], // expired
["ab3b3eb8a13311e78667001e673b2560", "class.class", "jane.doe@example.com", null], ["ab3b3eb8a13311e78667001e673b2560", "class.class", "jane.doe@example.com", null],
@ -54,7 +54,7 @@ trait SeriesCleanup {
], ],
'arsse_icons' => [ 'arsse_icons' => [
'columns' => ["id", "url", "orphaned"], 'columns' => ["id", "url", "orphaned"],
'rows' => [ 'rows' => [
[1,'http://localhost:8000/Icon/PNG',$daybefore], [1,'http://localhost:8000/Icon/PNG',$daybefore],
[2,'http://localhost:8000/Icon/GIF',$daybefore], [2,'http://localhost:8000/Icon/GIF',$daybefore],
[3,'http://localhost:8000/Icon/SVG1',null], [3,'http://localhost:8000/Icon/SVG1',null],
@ -62,7 +62,7 @@ trait SeriesCleanup {
], ],
'arsse_feeds' => [ 'arsse_feeds' => [
'columns' => ["id", "url", "title", "orphaned", "size", "icon"], 'columns' => ["id", "url", "title", "orphaned", "size", "icon"],
'rows' => [ 'rows' => [
[1,"http://example.com/1","",$daybefore,2,null], //latest two articles should be kept [1,"http://example.com/1","",$daybefore,2,null], //latest two articles should be kept
[2,"http://example.com/2","",$yesterday,0,2], [2,"http://example.com/2","",$yesterday,0,2],
[3,"http://example.com/3","",null,0,1], [3,"http://example.com/3","",null,0,1],
@ -71,7 +71,7 @@ trait SeriesCleanup {
], ],
'arsse_subscriptions' => [ 'arsse_subscriptions' => [
'columns' => ["id", "owner", "feed"], 'columns' => ["id", "owner", "feed"],
'rows' => [ 'rows' => [
// one feed previously marked for deletion has a subscription again, and so should not be deleted // one feed previously marked for deletion has a subscription again, and so should not be deleted
[1,'jane.doe@example.com',1], [1,'jane.doe@example.com',1],
// other subscriptions exist for article cleanup tests // other subscriptions exist for article cleanup tests
@ -80,7 +80,7 @@ trait SeriesCleanup {
], ],
'arsse_articles' => [ 'arsse_articles' => [
'columns' => ["id", "feed", "url_title_hash", "url_content_hash", "title_content_hash", "modified"], 'columns' => ["id", "feed", "url_title_hash", "url_content_hash", "title_content_hash", "modified"],
'rows' => [ 'rows' => [
[1,1,"","","",$weeksago], // is the latest article, thus is kept [1,1,"","","",$weeksago], // is the latest article, thus is kept
[2,1,"","","",$weeksago], // is the second 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 [3,1,"","","",$weeksago], // is starred by one user, thus is kept
@ -94,7 +94,7 @@ trait SeriesCleanup {
], ],
'arsse_editions' => [ 'arsse_editions' => [
'columns' => ["id", "article"], 'columns' => ["id", "article"],
'rows' => [ 'rows' => [
[1,1], [1,1],
[2,2], [2,2],
[3,3], [3,3],
@ -105,7 +105,7 @@ trait SeriesCleanup {
], ],
'arsse_marks' => [ 'arsse_marks' => [
'columns' => ["article", "subscription", "read", "starred", "hidden", "modified"], 'columns' => ["article", "subscription", "read", "starred", "hidden", "modified"],
'rows' => [ 'rows' => [
[3,1,0,1,0,$weeksago], [3,1,0,1,0,$weeksago],
[4,1,1,0,0,$daysago], [4,1,1,0,0,$daysago],
[6,1,1,0,0,$nowish], [6,1,1,0,0,$nowish],

18
tests/cases/Database/SeriesFeed.php

@ -18,14 +18,14 @@ trait SeriesFeed {
$this->data = [ $this->data = [
'arsse_users' => [ 'arsse_users' => [
'columns' => ["id", "password", "num"], 'columns' => ["id", "password", "num"],
'rows' => [ 'rows' => [
["jane.doe@example.com", "",1], ["jane.doe@example.com", "",1],
["john.doe@example.com", "",2], ["john.doe@example.com", "",2],
], ],
], ],
'arsse_icons' => [ 'arsse_icons' => [
'columns' => ["id", "url", "type", "data"], 'columns' => ["id", "url", "type", "data"],
'rows' => [ 'rows' => [
[1,'http://localhost:8000/Icon/PNG','image/png',base64_decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAAADUlEQVQYV2NgYGBgAAAABQABijPjAAAAAABJRU5ErkJggg==")], [1,'http://localhost:8000/Icon/PNG','image/png',base64_decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAAADUlEQVQYV2NgYGBgAAAABQABijPjAAAAAABJRU5ErkJggg==")],
[2,'http://localhost:8000/Icon/GIF','image/gif',base64_decode("R0lGODlhAQABAIABAAAAAP///yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==")], [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 // this actually contains the data of SVG2, which will lead to a row update when retieved
@ -34,7 +34,7 @@ trait SeriesFeed {
], ],
'arsse_feeds' => [ 'arsse_feeds' => [
'columns' => ["id", "url", "title", "err_count", "err_msg", "modified", "next_fetch", "size", "icon"], 'columns' => ["id", "url", "title", "err_count", "err_msg", "modified", "next_fetch", "size", "icon"],
'rows' => [ 'rows' => [
[1,"http://localhost:8000/Feed/Matching/3","Ook",0,"",$past,$past,0,null], [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], [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], [3,"http://localhost:8000/Feed/Fetching/Error?code=404","Ack",0,"",$past,$now,0,null],
@ -49,7 +49,7 @@ trait SeriesFeed {
], ],
'arsse_subscriptions' => [ 'arsse_subscriptions' => [
'columns' => ["id", "owner", "feed", "keep_rule", "block_rule"], 'columns' => ["id", "owner", "feed", "keep_rule", "block_rule"],
'rows' => [ 'rows' => [
[1,'john.doe@example.com',1,null,'^Sport$'], [1,'john.doe@example.com',1,null,'^Sport$'],
[2,'john.doe@example.com',2,"",null], [2,'john.doe@example.com',2,"",null],
[3,'john.doe@example.com',3,'\w+',null], [3,'john.doe@example.com',3,'\w+',null],
@ -60,7 +60,7 @@ trait SeriesFeed {
], ],
'arsse_articles' => [ 'arsse_articles' => [
'columns' => ["id", "feed", "url", "title", "author", "published", "edited", "content", "guid", "url_title_hash", "url_content_hash", "title_content_hash", "modified"], 'columns' => ["id", "feed", "url", "title", "author", "published", "edited", "content", "guid", "url_title_hash", "url_content_hash", "title_content_hash", "modified"],
'rows' => [ '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], [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], [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], [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],
@ -72,7 +72,7 @@ trait SeriesFeed {
], ],
'arsse_editions' => [ 'arsse_editions' => [
'columns' => ["id", "article", "modified"], 'columns' => ["id", "article", "modified"],
'rows' => [ 'rows' => [
[1,1,$past], [1,1,$past],
[2,2,$past], [2,2,$past],
[3,3,$past], [3,3,$past],
@ -82,7 +82,7 @@ trait SeriesFeed {
], ],
'arsse_marks' => [ 'arsse_marks' => [
'columns' => ["article", "subscription", "read", "starred", "hidden", "modified"], 'columns' => ["article", "subscription", "read", "starred", "hidden", "modified"],
'rows' => [ 'rows' => [
// Jane's marks // Jane's marks
[1,6,1,0,0,$past], [1,6,1,0,0,$past],
[2,6,1,0,0,$past], [2,6,1,0,0,$past],
@ -97,13 +97,13 @@ trait SeriesFeed {
], ],
'arsse_enclosures' => [ 'arsse_enclosures' => [
'columns' => ["article", "url", "type"], 'columns' => ["article", "url", "type"],
'rows' => [ 'rows' => [
[7,'http://example.com/png','image/png'], [7,'http://example.com/png','image/png'],
], ],
], ],
'arsse_categories' => [ 'arsse_categories' => [
'columns' => ["article", "name"], 'columns' => ["article", "name"],
'rows' => [ 'rows' => [
[7,'Syrinx'], [7,'Syrinx'],
], ],
], ],

6
tests/cases/Database/SeriesFolder.php

@ -13,7 +13,7 @@ trait SeriesFolder {
$this->data = [ $this->data = [
'arsse_users' => [ 'arsse_users' => [
'columns' => ["id", "password", "num"], 'columns' => ["id", "password", "num"],
'rows' => [ 'rows' => [
["jane.doe@example.com", "",1], ["jane.doe@example.com", "",1],
["john.doe@example.com", "",2], ["john.doe@example.com", "",2],
], ],
@ -41,7 +41,7 @@ trait SeriesFolder {
], ],
'arsse_feeds' => [ 'arsse_feeds' => [
'columns' => ["id", "url", "title"], 'columns' => ["id", "url", "title"],
'rows' => [ 'rows' => [
[1,"http://example.com/1", "Feed 1"], [1,"http://example.com/1", "Feed 1"],
[2,"http://example.com/2", "Feed 2"], [2,"http://example.com/2", "Feed 2"],
[3,"http://example.com/3", "Feed 3"], [3,"http://example.com/3", "Feed 3"],
@ -59,7 +59,7 @@ trait SeriesFolder {
], ],
'arsse_subscriptions' => [ 'arsse_subscriptions' => [
'columns' => ["id", "owner", "feed", "folder"], 'columns' => ["id", "owner", "feed", "folder"],
'rows' => [ 'rows' => [
[1, "john.doe@example.com",1, null], [1, "john.doe@example.com",1, null],
[2, "john.doe@example.com",2, null], [2, "john.doe@example.com",2, null],
[3, "john.doe@example.com",3, 1], [3, "john.doe@example.com",3, 1],

8
tests/cases/Database/SeriesIcon.php

@ -17,14 +17,14 @@ trait SeriesIcon {
$this->data = [ $this->data = [
'arsse_users' => [ 'arsse_users' => [
'columns' => ["id", "password", "num"], 'columns' => ["id", "password", "num"],
'rows' => [ 'rows' => [
["jane.doe@example.com", "",1], ["jane.doe@example.com", "",1],
["john.doe@example.com", "",2], ["john.doe@example.com", "",2],
], ],
], ],
'arsse_icons' => [ 'arsse_icons' => [
'columns' => ["id", "url", "type", "data"], 'columns' => ["id", "url", "type", "data"],
'rows' => [ 'rows' => [
[1,'http://localhost:8000/Icon/PNG','image/png',base64_decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAAADUlEQVQYV2NgYGBgAAAABQABijPjAAAAAABJRU5ErkJggg==")], [1,'http://localhost:8000/Icon/PNG','image/png',base64_decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAAADUlEQVQYV2NgYGBgAAAABQABijPjAAAAAABJRU5ErkJggg==")],
[2,'http://localhost:8000/Icon/GIF','image/gif',base64_decode("R0lGODlhAQABAIABAAAAAP///yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==")], [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>'], [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>'],
@ -33,7 +33,7 @@ trait SeriesIcon {
], ],
'arsse_feeds' => [ 'arsse_feeds' => [
'columns' => ["id", "url", "title", "err_count", "err_msg", "modified", "next_fetch", "size", "icon"], 'columns' => ["id", "url", "title", "err_count", "err_msg", "modified", "next_fetch", "size", "icon"],
'rows' => [ 'rows' => [
[1,"http://localhost:8000/Feed/Matching/3","Ook",0,"",$past,$past,0,1], [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], [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], [3,"http://localhost:8000/Feed/Fetching/Error?code=404","Ack",0,"",$past,$now,0,3],
@ -43,7 +43,7 @@ trait SeriesIcon {
], ],
'arsse_subscriptions' => [ 'arsse_subscriptions' => [
'columns' => ["id", "owner", "feed"], 'columns' => ["id", "owner", "feed"],
'rows' => [ 'rows' => [
[1,'john.doe@example.com',1], [1,'john.doe@example.com',1],
[2,'john.doe@example.com',2], [2,'john.doe@example.com',2],
[3,'john.doe@example.com',3], [3,'john.doe@example.com',3],

20
tests/cases/Database/SeriesLabel.php

@ -15,7 +15,7 @@ trait SeriesLabel {
$this->data = [ $this->data = [
'arsse_users' => [ 'arsse_users' => [
'columns' => ["id", "password", "num"], 'columns' => ["id", "password", "num"],
'rows' => [ 'rows' => [
["jane.doe@example.com", "",1], ["jane.doe@example.com", "",1],
["john.doe@example.com", "",2], ["john.doe@example.com", "",2],
["john.doe@example.org", "",3], ["john.doe@example.org", "",3],
@ -24,7 +24,7 @@ trait SeriesLabel {
], ],
'arsse_folders' => [ 'arsse_folders' => [
'columns' => ["id", "owner", "parent", "name"], 'columns' => ["id", "owner", "parent", "name"],
'rows' => [ 'rows' => [
[1, "john.doe@example.com", null, "Technology"], [1, "john.doe@example.com", null, "Technology"],
[2, "john.doe@example.com", 1, "Software"], [2, "john.doe@example.com", 1, "Software"],
[3, "john.doe@example.com", 1, "Rocketry"], [3, "john.doe@example.com", 1, "Rocketry"],
@ -38,7 +38,7 @@ trait SeriesLabel {
], ],
'arsse_feeds' => [ 'arsse_feeds' => [
'columns' => ["id", "url"], 'columns' => ["id", "url"],
'rows' => [ 'rows' => [
[1,"http://example.com/1"], [1,"http://example.com/1"],
[2,"http://example.com/2"], [2,"http://example.com/2"],
[3,"http://example.com/3"], [3,"http://example.com/3"],
@ -56,7 +56,7 @@ trait SeriesLabel {
], ],
'arsse_subscriptions' => [ 'arsse_subscriptions' => [
'columns' => ["id", "owner", "feed", "folder"], 'columns' => ["id", "owner", "feed", "folder"],
'rows' => [ 'rows' => [
[1,"john.doe@example.com",1,null], [1,"john.doe@example.com",1,null],
[2,"john.doe@example.com",2,null], [2,"john.doe@example.com",2,null],
[3,"john.doe@example.com",3,1], [3,"john.doe@example.com",3,1],
@ -75,7 +75,7 @@ trait SeriesLabel {
], ],
'arsse_articles' => [ 'arsse_articles' => [
'columns' => ["id", "feed", "url", "title", "author", "published", "edited", "content", "guid", "url_title_hash", "url_content_hash", "title_content_hash", "modified"], 'columns' => ["id", "feed", "url", "title", "author", "published", "edited", "content", "guid", "url_title_hash", "url_content_hash", "title_content_hash", "modified"],
'rows' => [ 'rows' => [
[1,1,null,null,null,null,null,null,null,"","","","2000-01-01T00:00:00Z"], [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"], [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"], [3,2,null,null,null,null,null,null,null,"","","","2000-01-01T00:00:00Z"],
@ -105,7 +105,7 @@ trait SeriesLabel {
], ],
'arsse_enclosures' => [ 'arsse_enclosures' => [
'columns' => ["article", "url", "type"], 'columns' => ["article", "url", "type"],
'rows' => [ 'rows' => [
[102,"http://example.com/text","text/plain"], [102,"http://example.com/text","text/plain"],
[103,"http://example.com/video","video/webm"], [103,"http://example.com/video","video/webm"],
[104,"http://example.com/image","image/svg+xml"], [104,"http://example.com/image","image/svg+xml"],
@ -115,7 +115,7 @@ trait SeriesLabel {
], ],
'arsse_editions' => [ 'arsse_editions' => [
'columns' => ["id", "article"], 'columns' => ["id", "article"],
'rows' => [ 'rows' => [
[1,1], [1,1],
[2,2], [2,2],
[3,3], [3,3],
@ -151,7 +151,7 @@ trait SeriesLabel {
], ],
'arsse_marks' => [ 'arsse_marks' => [
'columns' => ["subscription", "article", "read", "starred", "modified", "hidden"], 'columns' => ["subscription", "article", "read", "starred", "modified", "hidden"],
'rows' => [ 'rows' => [
[1, 1,1,1,'2000-01-01 00:00:00',0], [1, 1,1,1,'2000-01-01 00:00:00',0],
[5, 19,1,0,'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], [5, 20,0,1,'2010-01-01 00:00:00',0],
@ -169,7 +169,7 @@ trait SeriesLabel {
], ],
'arsse_labels' => [ 'arsse_labels' => [
'columns' => ["id", "owner", "name"], 'columns' => ["id", "owner", "name"],
'rows' => [ 'rows' => [
[1,"john.doe@example.com","Interesting"], [1,"john.doe@example.com","Interesting"],
[2,"john.doe@example.com","Fascinating"], [2,"john.doe@example.com","Fascinating"],
[3,"jane.doe@example.com","Boring"], [3,"jane.doe@example.com","Boring"],
@ -178,7 +178,7 @@ trait SeriesLabel {
], ],
'arsse_label_members' => [ 'arsse_label_members' => [
'columns' => ["label", "article", "subscription", "assigned"], 'columns' => ["label", "article", "subscription", "assigned"],
'rows' => [ 'rows' => [
[1, 1,1,1], [1, 1,1,1],
[2, 1,1,1], [2, 1,1,1],
[1,19,5,1], [1,19,5,1],

2
tests/cases/Database/SeriesMeta.php

@ -14,7 +14,7 @@ trait SeriesMeta {
$dataBare = [ $dataBare = [
'arsse_meta' => [ 'arsse_meta' => [
'columns' => ["key", "value"], 'columns' => ["key", "value"],
'rows' => [ 'rows' => [
//['schema_version', "".\JKingWeb\Arsse\Database::SCHEMA_VERSION], //['schema_version', "".\JKingWeb\Arsse\Database::SCHEMA_VERSION],
['album',"A Farewell to Kings"], ['album',"A Farewell to Kings"],
], ],

4
tests/cases/Database/SeriesSession.php

@ -24,14 +24,14 @@ trait SeriesSession {
$this->data = [ $this->data = [
'arsse_users' => [ 'arsse_users' => [
'columns' => ["id", "password", "num"], 'columns' => ["id", "password", "num"],
'rows' => [ 'rows' => [
["jane.doe@example.com", "",1], ["jane.doe@example.com", "",1],
["john.doe@example.com", "",2], ["john.doe@example.com", "",2],
], ],
], ],
'arsse_sessions' => [ 'arsse_sessions' => [
'columns' => ["id", "user", "created", "expires"], 'columns' => ["id", "user", "created", "expires"],
'rows' => [ 'rows' => [
["80fa94c1a11f11e78667001e673b2560", "jane.doe@example.com", $past, $faroff], ["80fa94c1a11f11e78667001e673b2560", "jane.doe@example.com", $past, $faroff],
["27c6de8da13311e78667001e673b2560", "jane.doe@example.com", $past, $past], // expired ["27c6de8da13311e78667001e673b2560", "jane.doe@example.com", $past, $past], // expired
["ab3b3eb8a13311e78667001e673b2560", "jane.doe@example.com", $old, $future], // too old ["ab3b3eb8a13311e78667001e673b2560", "jane.doe@example.com", $old, $future], // too old

22
tests/cases/Database/SeriesSubscription.php

@ -16,7 +16,7 @@ trait SeriesSubscription {
$this->data = [ $this->data = [
'arsse_users' => [ 'arsse_users' => [
'columns' => ["id", "password", "num"], 'columns' => ["id", "password", "num"],
'rows' => [ 'rows' => [
["jane.doe@example.com", "", 1], ["jane.doe@example.com", "", 1],
["john.doe@example.com", "", 2], ["john.doe@example.com", "", 2],
["jill.doe@example.com", "", 3], ["jill.doe@example.com", "", 3],
@ -25,7 +25,7 @@ trait SeriesSubscription {
], ],
'arsse_folders' => [ 'arsse_folders' => [
'columns' => ["id", "owner", "parent", "name"], 'columns' => ["id", "owner", "parent", "name"],
'rows' => [ 'rows' => [
[1, "john.doe@example.com", null, "Technology"], [1, "john.doe@example.com", null, "Technology"],
[2, "john.doe@example.com", 1, "Software"], [2, "john.doe@example.com", 1, "Software"],
[3, "john.doe@example.com", 1, "Rocketry"], [3, "john.doe@example.com", 1, "Rocketry"],
@ -36,14 +36,14 @@ trait SeriesSubscription {
], ],
'arsse_icons' => [ 'arsse_icons' => [
'columns' => ["id", "url", "data"], 'columns' => ["id", "url", "data"],
'rows' => [ 'rows' => [
[1,"http://example.com/favicon.ico", "ICON DATA"], [1,"http://example.com/favicon.ico", "ICON DATA"],
[2,"http://example.net/favicon.ico", null], [2,"http://example.net/favicon.ico", null],
], ],
], ],
'arsse_feeds' => [ 'arsse_feeds' => [
'columns' => ["id", "url", "title", "username", "password", "updated", "next_fetch", "icon"], 'columns' => ["id", "url", "title", "username", "password", "updated", "next_fetch", "icon"],
'rows' => [ 'rows' => [
[1,"http://example.com/feed1", "Ook", "", "",strtotime("now"),strtotime("now"),null], [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], [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], [3,"http://example.com/feed3", "Ack", "", "",strtotime("now + 1 hour"),strtotime("now + 1 hour"),2],
@ -52,7 +52,7 @@ trait SeriesSubscription {
], ],
'arsse_subscriptions' => [ 'arsse_subscriptions' => [
'columns' => ["id", "owner", "feed", "title", "folder", "pinned", "order_type", "keep_rule", "block_rule", "scrape"], 'columns' => ["id", "owner", "feed", "title", "folder", "pinned", "order_type", "keep_rule", "block_rule", "scrape"],
'rows' => [ 'rows' => [
[1,"john.doe@example.com",2,null,null,1,2,null,null,0], [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], [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], [3,"john.doe@example.com",3,"Ook",2,0,1,null,null,0],
@ -63,7 +63,7 @@ trait SeriesSubscription {
], ],
'arsse_tags' => [ 'arsse_tags' => [
'columns' => ["id", "owner", "name"], 'columns' => ["id", "owner", "name"],
'rows' => [ 'rows' => [
[1,"john.doe@example.com","Interesting"], [1,"john.doe@example.com","Interesting"],
[2,"john.doe@example.com","Fascinating"], [2,"john.doe@example.com","Fascinating"],
[3,"jane.doe@example.com","Boring"], [3,"jane.doe@example.com","Boring"],
@ -72,7 +72,7 @@ trait SeriesSubscription {
], ],
'arsse_tag_members' => [ 'arsse_tag_members' => [
'columns' => ["tag", "subscription", "assigned"], 'columns' => ["tag", "subscription", "assigned"],
'rows' => [ 'rows' => [
[1,1,1], [1,1,1],
[1,3,0], [1,3,0],
[2,1,1], [2,1,1],
@ -82,7 +82,7 @@ trait SeriesSubscription {
], ],
'arsse_articles' => [ 'arsse_articles' => [
'columns' => ["id", "feed", "url_title_hash", "url_content_hash", "title_content_hash", "title"], 'columns' => ["id", "feed", "url_title_hash", "url_content_hash", "title_content_hash", "title"],
'rows' => [ 'rows' => [
[1,2,"","","","Title 1"], [1,2,"","","","Title 1"],
[2,2,"","","","Title 2"], [2,2,"","","","Title 2"],
[3,2,"","","","Title 3"], [3,2,"","","","Title 3"],
@ -95,7 +95,7 @@ trait SeriesSubscription {
], ],
'arsse_editions' => [ 'arsse_editions' => [
'columns' => ["id", "article"], 'columns' => ["id", "article"],
'rows' => [ 'rows' => [
[1,1], [1,1],
[2,2], [2,2],
[3,3], [3,3],
@ -108,7 +108,7 @@ trait SeriesSubscription {
], ],
'arsse_categories' => [ 'arsse_categories' => [
'columns' => ["article", "name"], 'columns' => ["article", "name"],
'rows' => [ 'rows' => [
[1,"A"], [1,"A"],
[2,"B"], [2,"B"],
[4,"D"], [4,"D"],
@ -120,7 +120,7 @@ trait SeriesSubscription {
], ],
'arsse_marks' => [ 'arsse_marks' => [
'columns' => ["article", "subscription", "read", "starred", "hidden"], 'columns' => ["article", "subscription", "read", "starred", "hidden"],
'rows' => [ 'rows' => [
[1,2,1,0,0], [1,2,1,0,0],
[2,2,1,0,0], [2,2,1,0,0],
[3,2,1,0,0], [3,2,1,0,0],

10
tests/cases/Database/SeriesTag.php

@ -14,7 +14,7 @@ trait SeriesTag {
$this->data = [ $this->data = [
'arsse_users' => [ 'arsse_users' => [
'columns' => ["id", "password", "num"], 'columns' => ["id", "password", "num"],
'rows' => [ 'rows' => [
["jane.doe@example.com", "",1], ["jane.doe@example.com", "",1],
["john.doe@example.com", "",2], ["john.doe@example.com", "",2],
["john.doe@example.org", "",3], ["john.doe@example.org", "",3],
@ -23,7 +23,7 @@ trait SeriesTag {
], ],
'arsse_feeds' => [ 'arsse_feeds' => [
'columns' => ["id", "url", "title"], 'columns' => ["id", "url", "title"],
'rows' => [ 'rows' => [
[1,"http://example.com/1",""], [1,"http://example.com/1",""],
[2,"http://example.com/2",""], [2,"http://example.com/2",""],
[3,"http://example.com/3","Feed Title"], [3,"http://example.com/3","Feed Title"],
@ -41,7 +41,7 @@ trait SeriesTag {
], ],
'arsse_subscriptions' => [ 'arsse_subscriptions' => [
'columns' => ["id", "owner", "feed", "title"], 'columns' => ["id", "owner", "feed", "title"],
'rows' => [ 'rows' => [
[1, "john.doe@example.com", 1,"Lord of Carrots"], [1, "john.doe@example.com", 1,"Lord of Carrots"],
[2, "john.doe@example.com", 2,null], [2, "john.doe@example.com", 2,null],
[3, "john.doe@example.com", 3,"Subscription Title"], [3, "john.doe@example.com", 3,"Subscription Title"],
@ -60,7 +60,7 @@ trait SeriesTag {
], ],
'arsse_tags' => [ 'arsse_tags' => [
'columns' => ["id", "owner", "name"], 'columns' => ["id", "owner", "name"],
'rows' => [ 'rows' => [
[1,"john.doe@example.com","Interesting"], [1,"john.doe@example.com","Interesting"],
[2,"john.doe@example.com","Fascinating"], [2,"john.doe@example.com","Fascinating"],
[3,"jane.doe@example.com","Boring"], [3,"jane.doe@example.com","Boring"],
@ -69,7 +69,7 @@ trait SeriesTag {
], ],
'arsse_tag_members' => [ 'arsse_tag_members' => [
'columns' => ["tag", "subscription", "assigned"], 'columns' => ["tag", "subscription", "assigned"],
'rows' => [ 'rows' => [
[1,1,1], [1,1,1],
[1,3,0], [1,3,0],
[1,5,1], [1,5,1],

4
tests/cases/Database/SeriesToken.php

@ -18,14 +18,14 @@ trait SeriesToken {
$this->data = [ $this->data = [
'arsse_users' => [ 'arsse_users' => [
'columns' => ["id", "password", "num"], 'columns' => ["id", "password", "num"],
'rows' => [ 'rows' => [
["jane.doe@example.com", "",1], ["jane.doe@example.com", "",1],
["john.doe@example.com", "",2], ["john.doe@example.com", "",2],
], ],
], ],
'arsse_tokens' => [ 'arsse_tokens' => [
'columns' => ["id", "class", "user", "expires", "data"], 'columns' => ["id", "class", "user", "expires", "data"],
'rows' => [ 'rows' => [
["80fa94c1a11f11e78667001e673b2560", "fever.login", "jane.doe@example.com", $faroff, null], ["80fa94c1a11f11e78667001e673b2560", "fever.login", "jane.doe@example.com", $faroff, null],
["27c6de8da13311e78667001e673b2560", "fever.login", "jane.doe@example.com", $past, null], // expired ["27c6de8da13311e78667001e673b2560", "fever.login", "jane.doe@example.com", $past, null], // expired
["ab3b3eb8a13311e78667001e673b2560", "class.class", "jane.doe@example.com", null, null], ["ab3b3eb8a13311e78667001e673b2560", "class.class", "jane.doe@example.com", null, null],

4
tests/cases/Database/SeriesUser.php

@ -13,7 +13,7 @@ trait SeriesUser {
$this->data = [ $this->data = [
'arsse_users' => [ 'arsse_users' => [
'columns' => ["id", "password", "num", "admin"], 'columns' => ["id", "password", "num", "admin"],
'rows' => [ 'rows' => [
["admin@example.net", '$2y$10$PbcG2ZR3Z8TuPzM7aHTF8.v61dtCjzjK78gdZJcp4UePE8T9jEgBW', 1, 1], // password is hash of "secret" ["admin@example.net", '$2y$10$PbcG2ZR3Z8TuPzM7aHTF8.v61dtCjzjK78gdZJcp4UePE8T9jEgBW', 1, 1], // password is hash of "secret"
["jane.doe@example.com", "", 2, 0], ["jane.doe@example.com", "", 2, 0],
["john.doe@example.com", "", 3, 0], ["john.doe@example.com", "", 3, 0],
@ -21,7 +21,7 @@ trait SeriesUser {
], ],
'arsse_user_meta' => [ 'arsse_user_meta' => [
'columns' => ["owner", "key", "value"], 'columns' => ["owner", "key", "value"],
'rows' => [ 'rows' => [
["admin@example.net", "lang", "en"], ["admin@example.net", "lang", "en"],
["admin@example.net", "tz", "America/Toronto"], ["admin@example.net", "tz", "America/Toronto"],
["admin@example.net", "sort_asc", "0"], ["admin@example.net", "sort_asc", "0"],

12
tests/cases/ImportExport/TestImportExport.php

@ -42,14 +42,14 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
$this->data = [ $this->data = [
'arsse_users' => [ 'arsse_users' => [
'columns' => ["id", "password", "num"], 'columns' => ["id", "password", "num"],
'rows' => [ 'rows' => [
["john.doe@example.com", "", 1], ["john.doe@example.com", "", 1],
["jane.doe@example.com", "", 2], ["jane.doe@example.com", "", 2],
], ],
], ],
'arsse_folders' => [ 'arsse_folders' => [
'columns' => ["id", "owner", "parent", "name"], 'columns' => ["id", "owner", "parent", "name"],
'rows' => [ 'rows' => [
[1, "john.doe@example.com", null, "Science"], [1, "john.doe@example.com", null, "Science"],
[2, "john.doe@example.com", 1, "Rocketry"], [2, "john.doe@example.com", 1, "Rocketry"],
[3, "john.doe@example.com", null, "Politics"], [3, "john.doe@example.com", null, "Politics"],
@ -60,7 +60,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
], ],
'arsse_feeds' => [ 'arsse_feeds' => [
'columns' => ["id", "url", "title"], 'columns' => ["id", "url", "title"],
'rows' => [ 'rows' => [
[1, "http://localhost:8000/Import/nasa-jpl", "NASA JPL"], [1, "http://localhost:8000/Import/nasa-jpl", "NASA JPL"],
[2, "http://localhost:8000/Import/torstar", "Toronto Star"], [2, "http://localhost:8000/Import/torstar", "Toronto Star"],
[3, "http://localhost:8000/Import/ars", "Ars Technica"], [3, "http://localhost:8000/Import/ars", "Ars Technica"],
@ -71,7 +71,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
], ],
'arsse_subscriptions' => [ 'arsse_subscriptions' => [
'columns' => ["id", "owner", "folder", "feed", "title"], 'columns' => ["id", "owner", "folder", "feed", "title"],
'rows' => [ 'rows' => [
[1, "john.doe@example.com", 2, 1, "NASA JPL"], [1, "john.doe@example.com", 2, 1, "NASA JPL"],
[2, "john.doe@example.com", 5, 2, "Toronto Star"], [2, "john.doe@example.com", 5, 2, "Toronto Star"],
[3, "john.doe@example.com", 1, 3, "Ars Technica"], [3, "john.doe@example.com", 1, 3, "Ars Technica"],
@ -82,7 +82,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
], ],
'arsse_tags' => [ 'arsse_tags' => [
'columns' => ["id", "owner", "name"], 'columns' => ["id", "owner", "name"],
'rows' => [ 'rows' => [
[1, "john.doe@example.com", "canada"], [1, "john.doe@example.com", "canada"],
[2, "john.doe@example.com", "frequent"], [2, "john.doe@example.com", "frequent"],
[3, "john.doe@example.com", "gaming"], [3, "john.doe@example.com", "gaming"],
@ -93,7 +93,7 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
], ],
'arsse_tag_members' => [ 'arsse_tag_members' => [
'columns' => ["tag", "subscription", "assigned"], 'columns' => ["tag", "subscription", "assigned"],
'rows' => [ 'rows' => [
[1, 2, 1], [1, 2, 1],
[1, 4, 1], [1, 4, 1],
[1, 5, 1], [1, 5, 1],

27
tests/cases/Misc/TestHTTP.php

@ -7,14 +7,17 @@ declare(strict_types=1);
namespace JKingWeb\Arsse\TestCase\Misc; namespace JKingWeb\Arsse\TestCase\Misc;
use JKingWeb\Arsse\Misc\HTTP; use JKingWeb\Arsse\Misc\HTTP;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
/** @covers \JKingWeb\Arsse\Misc\HTTP */ /** @covers \JKingWeb\Arsse\Misc\HTTP */
class TestHTTP extends \JKingWeb\Arsse\Test\AbstractTest { class TestHTTP extends \JKingWeb\Arsse\Test\AbstractTest {
/** @dataProvider provideMediaTypes */ /** @dataProvider provideMediaTypes */
public function testMatchMediaType(string $header, array $types, bool $exp): void { public function testMatchMediaType(string $header, array $types, bool $exp): void {
$msg = (new \Laminas\Diactoros\Request)->withHeader("Content-Type", $header); $msg = (new Request("POST", "/"))->withHeader("Content-Type", $header);
$this->assertSame($exp, HTTP::matchType($msg, ...$types)); $this->assertSame($exp, HTTP::matchType($msg, ...$types));
$msg = (new \Laminas\Diactoros\Response)->withHeader("Content-Type", $header); $msg = (new Response)->withHeader("Content-Type", $header);
$this->assertSame($exp, HTTP::matchType($msg, ...$types)); $this->assertSame($exp, HTTP::matchType($msg, ...$types));
} }
@ -27,6 +30,26 @@ class TestHTTP extends \JKingWeb\Arsse\Test\AbstractTest {
["", ["application/json"], false], ["", ["application/json"], false],
["", ["application/json", ""], true], ["", ["application/json", ""], true],
["application/json ;", ["application/json"], true], ["application/json ;", ["application/json"], true],
["application/feed+json", ["application/json", "+json"], true],
["application/xhtml+xml", ["application/json", "+json"], false],
];
}
/** @dataProvider provideTypedMessages */
public function testCreateResponses(string $type, array $params, ResponseInterface $exp): void {
$act = call_user_func(["JKingWeb\\Arsse\\Misc\\HTTP", $type], ...$params);
$this->assertMessage($exp, $act);
}
public function provideTypedMessages(): iterable {
return [
["respEmpty", [422, ['Content-Length' => "0"]], new Response(422, ['Content-Length' => "0"])],
["respText", ["OOK"], new Response(200, ['Content-Type' => "text/plain; charset=UTF-8"], "OOK")],
["respText", ["OOK", 201, ['Content-Type' => "application/octet-stream"]], new Response(201, ['Content-Type' => "application/octet-stream"], "OOK")],
["respJson", [['ook' => "eek"]], new Response(200, ['Content-Type' => "application/json"], '{"ook":"eek"}')],
["respJson", [['ook' => "eek"], 400, ['Content-Type' => "application/feed+json"]], new Response(400, ['Content-Type' => "application/feed+json"], '{"ook":"eek"}')],
["respXml", ["<html/>"], new Response(200, ['Content-Type' => "application/xml; charset=UTF-8"], "<html/>")],
["respXml", ["<html/>", 451, ['Content-Type' => "text/plain", 'Vary' => "ETag"]], new Response(451, ['Content-Type' => "text/plain", 'Vary' => "ETag"], "<html/>")],
]; ];
} }
} }

50
tests/cases/REST/Fever/TestAPI.php

@ -9,15 +9,13 @@ namespace JKingWeb\Arsse\TestCase\REST\Fever;
use JKingWeb\Arsse\Arsse; use JKingWeb\Arsse\Arsse;
use JKingWeb\Arsse\User; use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Test\Result; use JKingWeb\Arsse\Test\Result;
use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Context\Context;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\Transaction; use JKingWeb\Arsse\Db\Transaction;
use JKingWeb\Arsse\REST\Fever\API; use JKingWeb\Arsse\REST\Fever\API;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse;
use Laminas\Diactoros\Response\XmlResponse;
use Laminas\Diactoros\Response\EmptyResponse;
/** @covers \JKingWeb\Arsse\REST\Fever\API<extended> */ /** @covers \JKingWeb\Arsse\REST\Fever\API<extended> */
class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest { class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
@ -192,9 +190,9 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
} }
public function provideTokenAuthenticationRequests(): iterable { public function provideTokenAuthenticationRequests(): iterable {
$success = new JsonResponse(['auth' => 1]); $success = HTTP::respJson(['auth' => 1]);
$failure = new JsonResponse(['auth' => 0]); $failure = HTTP::respJson(['auth' => 0]);
$denied = new EmptyResponse(401); $denied = HTTP::respEmpty(401);
return [ return [
[false, true, null, [], ['api' => null], $failure], [false, true, null, [], ['api' => null], $failure],
[false, false, null, [], ['api' => null], $failure], [false, false, null, [], ['api' => null], $failure],
@ -255,7 +253,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
['id' => 2, 'name' => "Interesting", 'subscription' => 1], ['id' => 2, 'name' => "Interesting", 'subscription' => 1],
['id' => 2, 'name' => "Interesting", 'subscription' => 3], ['id' => 2, 'name' => "Interesting", 'subscription' => 3],
])); ]));
$exp = new JsonResponse([ $exp = HTTP::respJson([
'groups' => [ 'groups' => [
['id' => 1, 'title' => "Fascinating"], ['id' => 1, 'title' => "Fascinating"],
['id' => 2, 'title' => "Interesting"], ['id' => 2, 'title' => "Interesting"],
@ -281,7 +279,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
['id' => 2, 'name' => "Interesting", 'subscription' => 1], ['id' => 2, 'name' => "Interesting", 'subscription' => 1],
['id' => 2, 'name' => "Interesting", 'subscription' => 3], ['id' => 2, 'name' => "Interesting", 'subscription' => 3],
])); ]));
$exp = new JsonResponse([ $exp = HTTP::respJson([
'feeds' => [ 'feeds' => [
['id' => 1, 'favicon_id' => 42, 'title' => "Ankh-Morpork News", 'url' => "http://example.com/feed", 'site_url' => "http://example.com/", 'is_spark' => 0, 'last_updated_on_time' => strtotime("2019-01-01T21:12:00Z")], ['id' => 1, 'favicon_id' => 42, 'title' => "Ankh-Morpork News", 'url' => "http://example.com/feed", 'site_url' => "http://example.com/", 'is_spark' => 0, 'last_updated_on_time' => strtotime("2019-01-01T21:12:00Z")],
['id' => 2, 'favicon_id' => 0, 'title' => "Ook, Ook Eek Ook!", 'url' => "http://example.net/feed", 'site_url' => "http://example.net/", 'is_spark' => 0, 'last_updated_on_time' => strtotime("1988-06-24T12:21:00Z")], ['id' => 2, 'favicon_id' => 0, 'title' => "Ook, Ook Eek Ook!", 'url' => "http://example.net/feed", 'site_url' => "http://example.net/", 'is_spark' => 0, 'last_updated_on_time' => strtotime("1988-06-24T12:21:00Z")],
@ -301,7 +299,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
$order = [$desc ? "id desc" : "id"]; $order = [$desc ? "id desc" : "id"];
$this->dbMock->articleList->returns(new Result($this->articles['db'])); $this->dbMock->articleList->returns(new Result($this->articles['db']));
$this->dbMock->articleCount->with($this->userId, (new Context)->hidden(false))->returns(1024); $this->dbMock->articleCount->with($this->userId, (new Context)->hidden(false))->returns(1024);
$exp = new JsonResponse([ $exp = HTTP::respJson([
'items' => $this->articles['rest'], 'items' => $this->articles['rest'],
'total_items' => 1024, 'total_items' => 1024,
]); ]);
@ -330,15 +328,15 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
$unread = [['id' => 4],['id' => 5],['id' => 6]]; $unread = [['id' => 4],['id' => 5],['id' => 6]];
$this->dbMock->articleList->with($this->userId, (new Context)->starred(true)->hidden(false))->returns(new Result($saved)); $this->dbMock->articleList->with($this->userId, (new Context)->starred(true)->hidden(false))->returns(new Result($saved));
$this->dbMock->articleList->with($this->userId, (new Context)->unread(true)->hidden(false))->returns(new Result($unread)); $this->dbMock->articleList->with($this->userId, (new Context)->unread(true)->hidden(false))->returns(new Result($unread));
$exp = new JsonResponse(['saved_item_ids' => "1,2,3"]); $exp = HTTP::respJson(['saved_item_ids' => "1,2,3"]);
$this->assertMessage($exp, $this->req("api&saved_item_ids")); $this->assertMessage($exp, $this->req("api&saved_item_ids"));
$exp = new JsonResponse(['unread_item_ids' => "4,5,6"]); $exp = HTTP::respJson(['unread_item_ids' => "4,5,6"]);
$this->assertMessage($exp, $this->req("api&unread_item_ids")); $this->assertMessage($exp, $this->req("api&unread_item_ids"));
} }
public function testListHotLinks(): void { public function testListHotLinks(): void {
// hot links are not actually implemented, so an empty array should be all we get // hot links are not actually implemented, so an empty array should be all we get
$exp = new JsonResponse(['links' => []]); $exp = HTTP::respJson(['links' => []]);
$this->assertMessage($exp, $this->req("api&links")); $this->assertMessage($exp, $this->req("api&links"));
} }
@ -350,7 +348,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->articleList->with($this->userId, (new Context)->unread(true)->hidden(false))->returns(new Result($unread)); $this->dbMock->articleList->with($this->userId, (new Context)->unread(true)->hidden(false))->returns(new Result($unread));
$this->dbMock->articleMark->returns(0); $this->dbMock->articleMark->returns(0);
$this->dbMock->articleMark->with($this->userId, $this->anything(), (new Context)->article(2112))->throws(new \JKingWeb\Arsse\Db\ExceptionInput("subjectMissing")); $this->dbMock->articleMark->with($this->userId, $this->anything(), (new Context)->article(2112))->throws(new \JKingWeb\Arsse\Db\ExceptionInput("subjectMissing"));
$exp = new JsonResponse($out); $exp = HTTP::respJson($out);
$this->assertMessage($exp, $this->req("api", $post)); $this->assertMessage($exp, $this->req("api", $post));
if ($c && $data) { if ($c && $data) {
$this->dbMock->articleMark->calledWith($this->userId, $data, $this->equalTo($c)); $this->dbMock->articleMark->calledWith($this->userId, $data, $this->equalTo($c));
@ -367,7 +365,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->articleList->with($this->userId, (new Context)->unread(true)->hidden(false))->returns(new Result($unread)); $this->dbMock->articleList->with($this->userId, (new Context)->unread(true)->hidden(false))->returns(new Result($unread));
$this->dbMock->articleMark->returns(0); $this->dbMock->articleMark->returns(0);
$this->dbMock->articleMark->with($this->userId, $this->anything(), (new Context)->article(2112))->throws(new \JKingWeb\Arsse\Db\ExceptionInput("subjectMissing")); $this->dbMock->articleMark->with($this->userId, $this->anything(), (new Context)->article(2112))->throws(new \JKingWeb\Arsse\Db\ExceptionInput("subjectMissing"));
$exp = new JsonResponse($out); $exp = HTTP::respJson($out);
$this->assertMessage($exp, $this->req("api&$get")); $this->assertMessage($exp, $this->req("api&$get"));
if ($c && $data) { if ($c && $data) {
$this->dbMock->articleMark->calledWith($this->userId, $data, $this->equalTo($c)); $this->dbMock->articleMark->calledWith($this->userId, $data, $this->equalTo($c));
@ -421,11 +419,11 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideInvalidRequests(): iterable { public function provideInvalidRequests(): iterable {
return [ return [
'Not an API request' => ["", "", "POST", null, new EmptyResponse(404)], 'Not an API request' => ["", "", "POST", null, HTTP::respEmpty(404)],
'Wrong method' => ["api", "", "PUT", null, new EmptyResponse(405, ['Allow' => "OPTIONS,POST"])], 'Wrong method' => ["api", "", "PUT", null, HTTP::respEmpty(405, ['Allow' => "OPTIONS,POST"])],
'Non-standard method' => ["api", "", "GET", null, new JsonResponse([])], 'Non-standard method' => ["api", "", "GET", null, HTTP::respJson([])],
'Wrong content type' => ["api", '{"api_key":"validToken"}', "POST", "application/json", new JsonResponse([])], // some clients send nonsensical content types; Fever seems to have allowed this 'Wrong content type' => ["api", '{"api_key":"validToken"}', "POST", "application/json", HTTP::respJson([])], // some clients send nonsensical content types; Fever seems to have allowed this
'Non-standard content type' => ["api", '{"api_key":"validToken"}', "POST", "multipart/form-data; boundary=33b68964f0de4c1f-5144aa6caaa6e4a8-18bfaf416a1786c8-5c5053a45f221bc1", new JsonResponse([])], // some clients send nonsensical content types; Fever seems to have allowed this 'Non-standard content type' => ["api", '{"api_key":"validToken"}', "POST", "multipart/form-data; boundary=33b68964f0de4c1f-5144aa6caaa6e4a8-18bfaf416a1786c8-5c5053a45f221bc1", HTTP::respJson([])], // some clients send nonsensical content types; Fever seems to have allowed this
]; ];
} }
@ -433,21 +431,21 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
$this->hMock->baseResponse->forwards(); $this->hMock->baseResponse->forwards();
$this->hMock->logIn->returns(true); $this->hMock->logIn->returns(true);
$this->dbMock->subscriptionRefreshed->with($this->userId)->returns(new \DateTimeImmutable("2000-01-01T00:00:00Z")); $this->dbMock->subscriptionRefreshed->with($this->userId)->returns(new \DateTimeImmutable("2000-01-01T00:00:00Z"));
$exp = new JsonResponse([ $exp = HTTP::respJson([
'api_version' => API::LEVEL, 'api_version' => API::LEVEL,
'auth' => 1, 'auth' => 1,
'last_refreshed_on_time' => 946684800, 'last_refreshed_on_time' => 946684800,
]); ]);
$this->assertMessage($exp, $this->req("api")); $this->assertMessage($exp, $this->req("api"));
$this->dbMock->subscriptionRefreshed->with($this->userId)->returns(null); // no subscriptions $this->dbMock->subscriptionRefreshed->with($this->userId)->returns(null); // no subscriptions
$exp = new JsonResponse([ $exp = HTTP::respJson([
'api_version' => API::LEVEL, 'api_version' => API::LEVEL,
'auth' => 1, 'auth' => 1,
'last_refreshed_on_time' => null, 'last_refreshed_on_time' => null,
]); ]);
$this->assertMessage($exp, $this->req("api")); $this->assertMessage($exp, $this->req("api"));
$this->hMock->logIn->returns(false); $this->hMock->logIn->returns(false);
$exp = new JsonResponse([ $exp = HTTP::respJson([
'api_version' => API::LEVEL, 'api_version' => API::LEVEL,
'auth' => 0, 'auth' => 0,
]); ]);
@ -460,7 +458,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->articleList->with($this->userId, $this->equalTo((new Context)->limit(1)->hidden(false)), ["marked_date"], ["marked_date desc"])->returns(new Result([['marked_date' => "2000-01-01 00:00:00"]])); $this->dbMock->articleList->with($this->userId, $this->equalTo((new Context)->limit(1)->hidden(false)), ["marked_date"], ["marked_date desc"])->returns(new Result([['marked_date' => "2000-01-01 00:00:00"]]));
$this->dbMock->articleList->with($this->userId, $this->equalTo((new Context)->unread(true)->hidden(false)))->returns(new Result($unread)); $this->dbMock->articleList->with($this->userId, $this->equalTo((new Context)->unread(true)->hidden(false)))->returns(new Result($unread));
$this->dbMock->articleMark->returns(0); $this->dbMock->articleMark->returns(0);
$exp = new JsonResponse($out); $exp = HTTP::respJson($out);
$this->assertMessage($exp, $this->req("api", ['unread_recently_read' => 1])); $this->assertMessage($exp, $this->req("api", ['unread_recently_read' => 1]));
$this->dbMock->articleMark->calledWith($this->userId, ['read' => false], $this->equalTo((new Context)->unread(false)->markedRange("1999-12-31T23:59:45Z", null)->hidden(false))); $this->dbMock->articleMark->calledWith($this->userId, ['read' => false], $this->equalTo((new Context)->unread(false)->markedRange("1999-12-31T23:59:45Z", null)->hidden(false)));
$this->dbMock->articleList->with($this->userId, (new Context)->limit(1)->hidden(false), ["marked_date"], ["marked_date desc"])->returns(new Result([])); $this->dbMock->articleList->with($this->userId, (new Context)->limit(1)->hidden(false), ["marked_date"], ["marked_date desc"])->returns(new Result([]));
@ -473,7 +471,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
'items' => $this->articles['rest'], 'items' => $this->articles['rest'],
'total_items' => 1024, 'total_items' => 1024,
]); ]);
$exp = new XmlResponse("<response><items><item><id>101</id><feed_id>8</feed_id><title>Article title 1</title><author></author><html>&lt;p&gt;Article content 1&lt;/p&gt;</html><url>http://example.com/1</url><is_saved>0</is_saved><is_read>0</is_read><created_on_time>946684800</created_on_time></item><item><id>102</id><feed_id>8</feed_id><title>Article title 2</title><author></author><html>&lt;p&gt;Article content 2&lt;/p&gt;</html><url>http://example.com/2</url><is_saved>0</is_saved><is_read>1</is_read><created_on_time>946771200</created_on_time></item><item><id>103</id><feed_id>9</feed_id><title>Article title 3</title><author></author><html>&lt;p&gt;Article content 3&lt;/p&gt;</html><url>http://example.com/3</url><is_saved>1</is_saved><is_read>0</is_read><created_on_time>946857600</created_on_time></item><item><id>104</id><feed_id>9</feed_id><title>Article title 4</title><author></author><html>&lt;p&gt;Article content 4&lt;/p&gt;</html><url>http://example.com/4</url><is_saved>1</is_saved><is_read>1</is_read><created_on_time>946944000</created_on_time></item><item><id>105</id><feed_id>10</feed_id><title>Article title 5</title><author></author><html>&lt;p&gt;Article content 5&lt;/p&gt;</html><url>http://example.com/5</url><is_saved>0</is_saved><is_read>0</is_read><created_on_time>947030400</created_on_time></item></items><total_items>1024</total_items></response>"); $exp = HTTP::respXml("<response><items><item><id>101</id><feed_id>8</feed_id><title>Article title 1</title><author></author><html>&lt;p&gt;Article content 1&lt;/p&gt;</html><url>http://example.com/1</url><is_saved>0</is_saved><is_read>0</is_read><created_on_time>946684800</created_on_time></item><item><id>102</id><feed_id>8</feed_id><title>Article title 2</title><author></author><html>&lt;p&gt;Article content 2&lt;/p&gt;</html><url>http://example.com/2</url><is_saved>0</is_saved><is_read>1</is_read><created_on_time>946771200</created_on_time></item><item><id>103</id><feed_id>9</feed_id><title>Article title 3</title><author></author><html>&lt;p&gt;Article content 3&lt;/p&gt;</html><url>http://example.com/3</url><is_saved>1</is_saved><is_read>0</is_read><created_on_time>946857600</created_on_time></item><item><id>104</id><feed_id>9</feed_id><title>Article title 4</title><author></author><html>&lt;p&gt;Article content 4&lt;/p&gt;</html><url>http://example.com/4</url><is_saved>1</is_saved><is_read>1</is_read><created_on_time>946944000</created_on_time></item><item><id>105</id><feed_id>10</feed_id><title>Article title 5</title><author></author><html>&lt;p&gt;Article content 5&lt;/p&gt;</html><url>http://example.com/5</url><is_saved>0</is_saved><is_read>0</is_read><created_on_time>947030400</created_on_time></item></items><total_items>1024</total_items></response>");
$this->assertMessage($exp, $this->req("api=xml")); $this->assertMessage($exp, $this->req("api=xml"));
} }
@ -485,7 +483,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
['id' => 44, 'type' => null, 'data' => "IMAGE DATA"], ['id' => 44, 'type' => null, 'data' => "IMAGE DATA"],
['id' => 47, 'type' => null, 'data' => null], ['id' => 47, 'type' => null, 'data' => null],
]))); ])));
$exp = new JsonResponse(['favicons' => [ $exp = HTTP::respJson(['favicons' => [
['id' => 0, 'data' => $iconType.",".$iconData], ['id' => 0, 'data' => $iconType.",".$iconData],
['id' => 42, 'data' => "image/svg+xml;base64,PHN2Zy8+"], ['id' => 42, 'data' => "image/svg+xml;base64,PHN2Zy8+"],
['id' => 44, 'data' => "application/octet-stream;base64,SU1BR0UgREFUQQ=="], ['id' => 44, 'data' => "application/octet-stream;base64,SU1BR0UgREFUQQ=="],
@ -494,7 +492,7 @@ class TestAPI extends \JKingWeb\Arsse\Test\AbstractTest {
} }
public function testAnswerOptionsRequest(): void { public function testAnswerOptionsRequest(): void {
$exp = new EmptyResponse(204, [ $exp = HTTP::respEmpty(204, [
'Allow' => "POST", 'Allow' => "POST",
'Accept' => "application/x-www-form-urlencoded, multipart/form-data", 'Accept' => "application/x-www-form-urlencoded, multipart/form-data",
]); ]);

22
tests/cases/REST/Miniflux/TestErrorResponse.php

@ -1,22 +0,0 @@
<?php
/** @license MIT
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse\TestCase\REST\Miniflux;
use JKingWeb\Arsse\REST\Miniflux\ErrorResponse;
/** @covers \JKingWeb\Arsse\REST\Miniflux\ErrorResponse */
class TestErrorResponse extends \JKingWeb\Arsse\Test\AbstractTest {
public function testCreateConstantResponse(): void {
$act = new ErrorResponse("401", 401);
$this->assertSame('{"error_message":"Access Unauthorized"}', (string) $act->getBody());
}
public function testCreateVariableResponse(): void {
$act = new ErrorResponse(["InvalidBodyJSON", "Doh!"], 401);
$this->assertSame('{"error_message":"Invalid JSON payload: Doh!"}', (string) $act->getBody());
}
}

17
tests/cases/REST/Miniflux/TestStatus.php

@ -6,11 +6,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\Arsse\TestCase\REST\Miniflux; namespace JKingWeb\Arsse\TestCase\REST\Miniflux;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\REST\Miniflux\Status; use JKingWeb\Arsse\REST\Miniflux\Status;
use JKingWeb\Arsse\REST\Miniflux\V1; use JKingWeb\Arsse\REST\Miniflux\V1;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\EmptyResponse;
use Laminas\Diactoros\Response\TextResponse;
/** @covers \JKingWeb\Arsse\REST\Miniflux\Status */ /** @covers \JKingWeb\Arsse\REST\Miniflux\Status */
class TestStatus extends \JKingWeb\Arsse\Test\AbstractTest { class TestStatus extends \JKingWeb\Arsse\Test\AbstractTest {
@ -22,13 +21,13 @@ class TestStatus extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideRequests(): iterable { public function provideRequests(): iterable {
return [ return [
["/version", "GET", new TextResponse(V1::VERSION)], ["/version", "GET", HTTP::respText(V1::VERSION)],
["/version", "POST", new EmptyResponse(405, ['Allow' => "HEAD, GET"])], ["/version", "POST", HTTP::respEmpty(405, ['Allow' => "HEAD, GET"])],
["/version", "OPTIONS", new EmptyResponse(204, ['Allow' => "HEAD, GET"])], ["/version", "OPTIONS", HTTP::respEmpty(204, ['Allow' => "HEAD, GET"])],
["/healthcheck", "GET", new TextResponse("OK")], ["/healthcheck", "GET", HTTP::respText("OK")],
["/healthcheck", "POST", new EmptyResponse(405, ['Allow' => "HEAD, GET"])], ["/healthcheck", "POST", HTTP::respEmpty(405, ['Allow' => "HEAD, GET"])],
["/healthcheck", "OPTIONS", new EmptyResponse(204, ['Allow' => "HEAD, GET"])], ["/healthcheck", "OPTIONS", HTTP::respEmpty(204, ['Allow' => "HEAD, GET"])],
["/version/", "GET", new EmptyResponse(404)], ["/version/", "GET", HTTP::respEmpty(404)],
]; ];
} }
} }

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

@ -14,10 +14,10 @@ use JKingWeb\Arsse\Context\RootContext;
use JKingWeb\Arsse\Context\UnionContext; use JKingWeb\Arsse\Context\UnionContext;
use JKingWeb\Arsse\User; use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Db\Transaction; use JKingWeb\Arsse\Db\Transaction;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\REST\Miniflux\V1; use JKingWeb\Arsse\REST\Miniflux\V1;
use JKingWeb\Arsse\REST\Miniflux\ErrorResponse;
use JKingWeb\Arsse\Feed\Exception as FeedException; use JKingWeb\Arsse\Feed\Exception as FeedException;
use JKingWeb\Arsse\ImportExport\Exception as ImportException; use JKingWeb\Arsse\ImportExport\Exception as ImportException;
use JKingWeb\Arsse\ImportExport\OPML; use JKingWeb\Arsse\ImportExport\OPML;
@ -25,9 +25,6 @@ use JKingWeb\Arsse\User\ExceptionConflict;
use JKingWeb\Arsse\User\ExceptionInput as UserExceptionInput; use JKingWeb\Arsse\User\ExceptionInput as UserExceptionInput;
use JKingWeb\Arsse\Test\Result; use JKingWeb\Arsse\Test\Result;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse;
use Laminas\Diactoros\Response\TextResponse;
/** @covers \JKingWeb\Arsse\REST\Miniflux\V1<extended> */ /** @covers \JKingWeb\Arsse\REST\Miniflux\V1<extended> */
class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest { class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
@ -100,9 +97,15 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
return $value; return $value;
} }
public function testGenerateErrorResponse() {
$act = V1::respError(["DuplicateUser", 'user' => "john.doe"], 409, ['Cache-Control' => "no-store"]);
$exp = HTTP::respJson(['error_message' => 'The user name "john.doe" already exists'], 409, ['Cache-Control' => "no-store"]);
$this->assertMessage($exp, $act);
}
/** @dataProvider provideAuthResponses */ /** @dataProvider provideAuthResponses */
public function testAuthenticateAUser($token, bool $auth, bool $success): void { public function testAuthenticateAUser($token, bool $auth, bool $success): void {
$exp = $success ? new EmptyResponse(404) : new ErrorResponse("401", 401); $exp = $success ? HTTP::respEmpty(404) : V1::respError("401", 401);
$user = "john.doe@example.com"; $user = "john.doe@example.com";
if ($token !== null) { if ($token !== null) {
$headers = ['X-Auth-Token' => $token]; $headers = ['X-Auth-Token' => $token];
@ -133,7 +136,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
/** @dataProvider provideInvalidPaths */ /** @dataProvider provideInvalidPaths */
public function testRespondToInvalidPaths($path, $method, $code, $allow = null): void { public function testRespondToInvalidPaths($path, $method, $code, $allow = null): void {
$exp = new EmptyResponse($code, $allow ? ['Allow' => $allow] : []); $exp = HTTP::respEmpty($code, $allow ? ['Allow' => $allow] : []);
$this->assertMessage($exp, $this->req($method, $path)); $this->assertMessage($exp, $this->req($method, $path));
} }
@ -148,7 +151,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
/** @dataProvider provideOptionsRequests */ /** @dataProvider provideOptionsRequests */
public function testRespondToOptionsRequests(string $url, string $allow, string $accept): void { public function testRespondToOptionsRequests(string $url, string $allow, string $accept): void {
$exp = new EmptyResponse(204, [ $exp = HTTP::respEmpty(204, [
'Allow' => $allow, 'Allow' => $allow,
'Accept' => $accept, 'Accept' => $accept,
]); ]);
@ -166,7 +169,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
} }
public function testRejectBadlyTypedData(): void { public function testRejectBadlyTypedData(): void {
$exp = new ErrorResponse(["InvalidInputType", 'field' => "url", 'expected' => "string", 'actual' => "integer"], 422); $exp = V1::respError(["InvalidInputType", 'field' => "url", 'expected' => "string", 'actual' => "integer"], 422);
$this->assertMessage($exp, $this->req("POST", "/discover", ['url' => 2112])); $this->assertMessage($exp, $this->req("POST", "/discover", ['url' => 2112]));
} }
@ -182,12 +185,12 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
['title' => "Feed", 'type' => "rss", 'url' => "http://localhost:8000/Feed/Discovery/Missing"], ['title' => "Feed", 'type' => "rss", 'url' => "http://localhost:8000/Feed/Discovery/Missing"],
]; ];
return [ return [
["http://localhost:8000/Feed/Discovery/Valid", new Response($discovered)], ["http://localhost:8000/Feed/Discovery/Valid", HTTP::respJson($discovered)],
["http://localhost:8000/Feed/Discovery/Invalid", new Response([])], ["http://localhost:8000/Feed/Discovery/Invalid", HTTP::respJson([])],
["http://localhost:8000/Feed/Discovery/Missing", new ErrorResponse("Fetch404", 502)], ["http://localhost:8000/Feed/Discovery/Missing", V1::respError("Fetch404", 502)],
[1, new ErrorResponse(["InvalidInputType", 'field' => "url", 'expected' => "string", 'actual' => "integer"], 422)], [1, V1::respError(["InvalidInputType", 'field' => "url", 'expected' => "string", 'actual' => "integer"], 422)],
["Not a URL", new ErrorResponse(["InvalidInputValue", 'field' => "url"], 422)], ["Not a URL", V1::respError(["InvalidInputValue", 'field' => "url"], 422)],
[null, new ErrorResponse(["MissingInputValue", 'field' => "url"], 422)], [null, V1::respError(["MissingInputValue", 'field' => "url"], 422)],
]; ];
} }
@ -226,22 +229,22 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideUserQueries(): iterable { public function provideUserQueries(): iterable {
self::clearData(); self::clearData();
return [ return [
[true, "/users", new Response(self::USERS)], [true, "/users", HTTP::respJson(self::USERS)],
[true, "/me", new Response(self::USERS[0])], [true, "/me", HTTP::respJson(self::USERS[0])],
[true, "/users/john.doe@example.com", new Response(self::USERS[0])], [true, "/users/john.doe@example.com", HTTP::respJson(self::USERS[0])],
[true, "/users/1", new Response(self::USERS[0])], [true, "/users/1", HTTP::respJson(self::USERS[0])],
[true, "/users/jane.doe@example.com", new Response(self::USERS[1])], [true, "/users/jane.doe@example.com", HTTP::respJson(self::USERS[1])],
[true, "/users/2", new Response(self::USERS[1])], [true, "/users/2", HTTP::respJson(self::USERS[1])],
[true, "/users/jack.doe@example.com", new ErrorResponse("404", 404)], [true, "/users/jack.doe@example.com", V1::respError("404", 404)],
[true, "/users/47", new ErrorResponse("404", 404)], [true, "/users/47", V1::respError("404", 404)],
[false, "/users", new ErrorResponse("403", 403)], [false, "/users", V1::respError("403", 403)],
[false, "/me", new Response(self::USERS[1])], [false, "/me", HTTP::respJson(self::USERS[1])],
[false, "/users/john.doe@example.com", new ErrorResponse("403", 403)], [false, "/users/john.doe@example.com", V1::respError("403", 403)],
[false, "/users/1", new ErrorResponse("403", 403)], [false, "/users/1", V1::respError("403", 403)],
[false, "/users/jane.doe@example.com", new ErrorResponse("403", 403)], [false, "/users/jane.doe@example.com", V1::respError("403", 403)],
[false, "/users/2", new ErrorResponse("403", 403)], [false, "/users/2", V1::respError("403", 403)],
[false, "/users/jack.doe@example.com", new ErrorResponse("403", 403)], [false, "/users/jack.doe@example.com", V1::respError("403", 403)],
[false, "/users/47", new ErrorResponse("403", 403)], [false, "/users/47", V1::respError("403", 403)],
]; ];
} }
@ -306,21 +309,21 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
$resp1 = array_merge(self::USERS[1], ['username' => "john.doe@example.com"]); $resp1 = array_merge(self::USERS[1], ['username' => "john.doe@example.com"]);
$resp2 = array_merge(self::USERS[1], ['id' => 1, 'is_admin' => true]); $resp2 = array_merge(self::USERS[1], ['id' => 1, 'is_admin' => true]);
return [ return [
[false, "/users/1", ['is_admin' => 0], null, null, null, null, null, null, new ErrorResponse(["InvalidInputType", 'field' => "is_admin", 'expected' => "boolean", 'actual' => "integer"], 422)], [false, "/users/1", ['is_admin' => 0], null, null, null, null, null, null, V1::respError(["InvalidInputType", 'field' => "is_admin", 'expected' => "boolean", 'actual' => "integer"], 422)],
[false, "/users/1", ['entry_sorting_direction' => "bad"], null, null, null, null, null, null, new ErrorResponse(["InvalidInputValue", 'field' => "entry_sorting_direction"], 422)], [false, "/users/1", ['entry_sorting_direction' => "bad"], null, null, null, null, null, null, V1::respError(["InvalidInputValue", 'field' => "entry_sorting_direction"], 422)],
[false, "/users/1", ['theme' => "stark"], null, null, null, null, null, null, new ErrorResponse("403", 403)], [false, "/users/1", ['theme' => "stark"], null, null, null, null, null, null, V1::respError("403", 403)],
[false, "/users/2", ['is_admin' => true], null, null, null, null, null, null, new ErrorResponse("InvalidElevation", 403)], [false, "/users/2", ['is_admin' => true], null, null, null, null, null, null, V1::respError("InvalidElevation", 403)],
[false, "/users/2", ['language' => "fr_CA"], null, null, null, null, ['lang' => "fr_CA"], $out1, new Response($resp1, 201)], [false, "/users/2", ['language' => "fr_CA"], null, null, null, null, ['lang' => "fr_CA"], $out1, HTTP::respJson($resp1, 201)],
[false, "/users/2", ['entry_sorting_direction' => "asc"], null, null, null, null, ['sort_asc' => true], $out1, new Response($resp1, 201)], [false, "/users/2", ['entry_sorting_direction' => "asc"], null, null, null, null, ['sort_asc' => true], $out1, HTTP::respJson($resp1, 201)],
[false, "/users/2", ['entry_sorting_direction' => "desc"], null, null, null, null, ['sort_asc' => false], $out1, new Response($resp1, 201)], [false, "/users/2", ['entry_sorting_direction' => "desc"], null, null, null, null, ['sort_asc' => false], $out1, HTTP::respJson($resp1, 201)],
[false, "/users/2", ['entries_per_page' => -1], null, null, null, null, ['page_size' => -1], new UserExceptionInput("invalidNonZeroInteger"), new ErrorResponse(["InvalidInputValue", 'field' => "entries_per_page"], 422)], [false, "/users/2", ['entries_per_page' => -1], null, null, null, null, ['page_size' => -1], new UserExceptionInput("invalidNonZeroInteger"), V1::respError(["InvalidInputValue", 'field' => "entries_per_page"], 422)],
[false, "/users/2", ['timezone' => "Ook"], null, null, null, null, ['tz' => "Ook"], new UserExceptionInput("invalidTimezone"), new ErrorResponse(["InvalidInputValue", 'field' => "timezone"], 422)], [false, "/users/2", ['timezone' => "Ook"], null, null, null, null, ['tz' => "Ook"], new UserExceptionInput("invalidTimezone"), V1::respError(["InvalidInputValue", 'field' => "timezone"], 422)],
[false, "/users/2", ['username' => "j:k"], "j:k", new UserExceptionInput("invalidUsername"), null, null, null, null, new ErrorResponse(["InvalidInputValue", 'field' => "username"], 422)], [false, "/users/2", ['username' => "j:k"], "j:k", new UserExceptionInput("invalidUsername"), null, null, null, null, V1::respError(["InvalidInputValue", 'field' => "username"], 422)],
[false, "/users/2", ['username' => "ook"], "ook", new ExceptionConflict("alreadyExists"), null, null, null, null, new ErrorResponse(["DuplicateUser", 'user' => "ook"], 409)], [false, "/users/2", ['username' => "ook"], "ook", new ExceptionConflict("alreadyExists"), null, null, null, null, V1::respError(["DuplicateUser", 'user' => "ook"], 409)],
[false, "/users/2", ['password' => "ook"], null, null, "ook", "ook", null, null, new Response(array_merge($resp1, ['password' => "ook"]), 201)], [false, "/users/2", ['password' => "ook"], null, null, "ook", "ook", null, null, HTTP::respJson(array_merge($resp1, ['password' => "ook"]), 201)],
[false, "/users/2", ['username' => "ook", 'password' => "ook"], "ook", true, "ook", "ook", null, null, new Response(array_merge($resp1, ['username' => "ook", 'password' => "ook"]), 201)], [false, "/users/2", ['username' => "ook", 'password' => "ook"], "ook", true, "ook", "ook", null, null, HTTP::respJson(array_merge($resp1, ['username' => "ook", 'password' => "ook"]), 201)],
[true, "/users/1", ['theme' => "stark"], null, null, null, null, ['theme' => "stark"], $out2, new Response($resp2, 201)], [true, "/users/1", ['theme' => "stark"], null, null, null, null, ['theme' => "stark"], $out2, HTTP::respJson($resp2, 201)],
[true, "/users/3", ['theme' => "stark"], null, null, null, null, null, null, new ErrorResponse("404", 404)], [true, "/users/3", ['theme' => "stark"], null, null, null, null, null, null, V1::respError("404", 404)],
]; ];
} }
@ -361,18 +364,18 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideUserAdditions(): iterable { public function provideUserAdditions(): iterable {
$resp1 = array_merge(self::USERS[1], ['username' => "ook", 'password' => "eek"]); $resp1 = array_merge(self::USERS[1], ['username' => "ook", 'password' => "eek"]);
return [ return [
[[], null, null, null, null, new ErrorResponse(["MissingInputValue", 'field' => "username"], 422)], [[], null, null, null, null, V1::respError(["MissingInputValue", 'field' => "username"], 422)],
[['username' => "ook"], null, null, null, null, new ErrorResponse(["MissingInputValue", 'field' => "password"], 422)], [['username' => "ook"], null, null, null, null, V1::respError(["MissingInputValue", 'field' => "password"], 422)],
[['username' => "ook", 'password' => "eek"], ["ook", "eek"], new ExceptionConflict("alreadyExists"), null, null, new ErrorResponse(["DuplicateUser", 'user' => "ook"], 409)], [['username' => "ook", 'password' => "eek"], ["ook", "eek"], new ExceptionConflict("alreadyExists"), null, null, V1::respError(["DuplicateUser", 'user' => "ook"], 409)],
[['username' => "j:k", 'password' => "eek"], ["j:k", "eek"], new UserExceptionInput("invalidUsername"), null, null, new ErrorResponse(["InvalidInputValue", 'field' => "username"], 422)], [['username' => "j:k", 'password' => "eek"], ["j:k", "eek"], new UserExceptionInput("invalidUsername"), null, null, V1::respError(["InvalidInputValue", 'field' => "username"], 422)],
[['username' => "ook", 'password' => "eek", 'timezone' => "ook"], ["ook", "eek"], "eek", ['tz' => "ook"], new UserExceptionInput("invalidTimezone"), new ErrorResponse(["InvalidInputValue", 'field' => "timezone"], 422)], [['username' => "ook", 'password' => "eek", 'timezone' => "ook"], ["ook", "eek"], "eek", ['tz' => "ook"], new UserExceptionInput("invalidTimezone"), V1::respError(["InvalidInputValue", 'field' => "timezone"], 422)],
[['username' => "ook", 'password' => "eek", 'entries_per_page' => -1], ["ook", "eek"], "eek", ['page_size' => -1], new UserExceptionInput("invalidNonZeroInteger"), new ErrorResponse(["InvalidInputValue", 'field' => "entries_per_page"], 422)], [['username' => "ook", 'password' => "eek", 'entries_per_page' => -1], ["ook", "eek"], "eek", ['page_size' => -1], new UserExceptionInput("invalidNonZeroInteger"), V1::respError(["InvalidInputValue", 'field' => "entries_per_page"], 422)],
[['username' => "ook", 'password' => "eek", 'theme' => "default"], ["ook", "eek"], "eek", ['theme' => "default"], ['theme' => "default"], new Response($resp1, 201)], [['username' => "ook", 'password' => "eek", 'theme' => "default"], ["ook", "eek"], "eek", ['theme' => "default"], ['theme' => "default"], HTTP::respJson($resp1, 201)],
]; ];
} }
public function testAddAUserWithoutAuthority(): void { public function testAddAUserWithoutAuthority(): void {
$this->assertMessage(new ErrorResponse("403", 403), $this->req("POST", "/users", [])); $this->assertMessage(V1::respError("403", 403), $this->req("POST", "/users", []));
} }
public function testDeleteAUser(): void { public function testDeleteAUser(): void {
@ -382,7 +385,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
Arsse::$user->method("remove")->willReturn(true); Arsse::$user->method("remove")->willReturn(true);
Arsse::$user->expects($this->exactly(1))->method("lookup")->with(2112); Arsse::$user->expects($this->exactly(1))->method("lookup")->with(2112);
Arsse::$user->expects($this->exactly(1))->method("remove")->with("john.doe@example.com"); Arsse::$user->expects($this->exactly(1))->method("remove")->with("john.doe@example.com");
$this->assertMessage(new EmptyResponse(204), $this->req("DELETE", "/users/2112")); $this->assertMessage(HTTP::respEmpty(204), $this->req("DELETE", "/users/2112"));
} }
public function testDeleteAMissingUser(): void { public function testDeleteAMissingUser(): void {
@ -392,13 +395,13 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
Arsse::$user->method("remove")->willReturn(true); Arsse::$user->method("remove")->willReturn(true);
Arsse::$user->expects($this->exactly(1))->method("lookup")->with(2112); Arsse::$user->expects($this->exactly(1))->method("lookup")->with(2112);
Arsse::$user->expects($this->exactly(0))->method("remove"); Arsse::$user->expects($this->exactly(0))->method("remove");
$this->assertMessage(new ErrorResponse("404", 404), $this->req("DELETE", "/users/2112")); $this->assertMessage(V1::respError("404", 404), $this->req("DELETE", "/users/2112"));
} }
public function testDeleteAUserWithoutAuthority(): void { public function testDeleteAUserWithoutAuthority(): void {
Arsse::$user->expects($this->exactly(0))->method("lookup"); Arsse::$user->expects($this->exactly(0))->method("lookup");
Arsse::$user->expects($this->exactly(0))->method("remove"); Arsse::$user->expects($this->exactly(0))->method("remove");
$this->assertMessage(new ErrorResponse("403", 403), $this->req("DELETE", "/users/2112")); $this->assertMessage(V1::respError("403", 403), $this->req("DELETE", "/users/2112"));
} }
public function testListCategories(): void { public function testListCategories(): void {
@ -406,7 +409,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
['id' => 1, 'name' => "Science"], ['id' => 1, 'name' => "Science"],
['id' => 20, 'name' => "Technology"], ['id' => 20, 'name' => "Technology"],
]))); ])));
$exp = new Response([ $exp = HTTP::respJson([
['id' => 1, 'title' => "All", 'user_id' => 42], ['id' => 1, 'title' => "All", 'user_id' => 42],
['id' => 2, 'title' => "Science", 'user_id' => 42], ['id' => 2, 'title' => "Science", 'user_id' => 42],
['id' => 21, 'title' => "Technology", 'user_id' => 42], ['id' => 21, 'title' => "Technology", 'user_id' => 42],
@ -416,7 +419,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
// run test again with a renamed root folder // run test again with a renamed root folder
Arsse::$user = $this->createMock(User::class); Arsse::$user = $this->createMock(User::class);
Arsse::$user->method("propertiesGet")->willReturn(['num' => 47, 'admin' => false, 'root_folder_name' => "Uncategorized"]); Arsse::$user->method("propertiesGet")->willReturn(['num' => 47, 'admin' => false, 'root_folder_name' => "Uncategorized"]);
$exp = new Response([ $exp = HTTP::respJson([
['id' => 1, 'title' => "Uncategorized", 'user_id' => 47], ['id' => 1, 'title' => "Uncategorized", 'user_id' => 47],
['id' => 2, 'title' => "Science", 'user_id' => 47], ['id' => 2, 'title' => "Science", 'user_id' => 47],
['id' => 21, 'title' => "Technology", 'user_id' => 47], ['id' => 21, 'title' => "Technology", 'user_id' => 47],
@ -440,12 +443,12 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideCategoryAdditions(): iterable { public function provideCategoryAdditions(): iterable {
return [ return [
["New", new Response(['id' => 2112, 'title' => "New", 'user_id' => 42], 201)], ["New", HTTP::respJson(['id' => 2112, 'title' => "New", 'user_id' => 42], 201)],
["Duplicate", new ErrorResponse(["DuplicateCategory", 'title' => "Duplicate"], 409)], ["Duplicate", V1::respError(["DuplicateCategory", 'title' => "Duplicate"], 409)],
["", new ErrorResponse(["InvalidCategory", 'title' => ""], 422)], ["", V1::respError(["InvalidCategory", 'title' => ""], 422)],
[" ", new ErrorResponse(["InvalidCategory", 'title' => " "], 422)], [" ", V1::respError(["InvalidCategory", 'title' => " "], 422)],
[null, new ErrorResponse(["MissingInputValue", 'field' => "title"], 422)], [null, V1::respError(["MissingInputValue", 'field' => "title"], 422)],
[false, new ErrorResponse(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)], [false, V1::respError(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)],
]; ];
} }
@ -466,27 +469,27 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideCategoryUpdates(): iterable { public function provideCategoryUpdates(): iterable {
return [ return [
[3, "New", "subjectMissing", new ErrorResponse("404", 404)], [3, "New", "subjectMissing", V1::respError("404", 404)],
[2, "New", true, new Response(['id' => 2, 'title' => "New", 'user_id' => 42], 201)], [2, "New", true, HTTP::respJson(['id' => 2, 'title' => "New", 'user_id' => 42], 201)],
[2, "Duplicate", "constraintViolation", new ErrorResponse(["DuplicateCategory", 'title' => "Duplicate"], 409)], [2, "Duplicate", "constraintViolation", V1::respError(["DuplicateCategory", 'title' => "Duplicate"], 409)],
[2, "", "missing", new ErrorResponse(["InvalidCategory", 'title' => ""], 422)], [2, "", "missing", V1::respError(["InvalidCategory", 'title' => ""], 422)],
[2, " ", "whitespace", new ErrorResponse(["InvalidCategory", 'title' => " "], 422)], [2, " ", "whitespace", V1::respError(["InvalidCategory", 'title' => " "], 422)],
[2, null, "missing", new ErrorResponse(["MissingInputValue", 'field' => "title"], 422)], [2, null, "missing", V1::respError(["MissingInputValue", 'field' => "title"], 422)],
[2, false, "subjectMissing", new ErrorResponse(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)], [2, false, "subjectMissing", V1::respError(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)],
[1, "New", true, new Response(['id' => 1, 'title' => "New", 'user_id' => 42], 201)], [1, "New", true, HTTP::respJson(['id' => 1, 'title' => "New", 'user_id' => 42], 201)],
[1, "Duplicate", "constraintViolation", new Response(['id' => 1, 'title' => "Duplicate", 'user_id' => 42], 201)], // This is allowed because the name of the root folder is only a duplicate in circumstances where it is used [1, "Duplicate", "constraintViolation", HTTP::respJson(['id' => 1, 'title' => "Duplicate", 'user_id' => 42], 201)], // This is allowed because the name of the root folder is only a duplicate in circumstances where it is used
[1, "", "missing", new ErrorResponse(["InvalidCategory", 'title' => ""], 422)], [1, "", "missing", V1::respError(["InvalidCategory", 'title' => ""], 422)],
[1, " ", "whitespace", new ErrorResponse(["InvalidCategory", 'title' => " "], 422)], [1, " ", "whitespace", V1::respError(["InvalidCategory", 'title' => " "], 422)],
[1, null, "missing", new ErrorResponse(["MissingInputValue", 'field' => "title"], 422)], [1, null, "missing", V1::respError(["MissingInputValue", 'field' => "title"], 422)],
[1, false, false, new ErrorResponse(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)], [1, false, false, V1::respError(["InvalidInputType", 'field' => "title", 'actual' => "boolean", 'expected' => "string"], 422)],
]; ];
} }
public function testDeleteARealCategory(): void { public function testDeleteARealCategory(): void {
$this->dbMock->folderRemove->returns(true)->throws(new ExceptionInput("subjectMissing")); $this->dbMock->folderRemove->returns(true)->throws(new ExceptionInput("subjectMissing"));
$this->assertMessage(new EmptyResponse(204), $this->req("DELETE", "/categories/2112")); $this->assertMessage(HTTP::respEmpty(204), $this->req("DELETE", "/categories/2112"));
$this->dbMock->folderRemove->calledWith("john.doe@example.com", 2111); $this->dbMock->folderRemove->calledWith("john.doe@example.com", 2111);
$this->assertMessage(new ErrorResponse("404", 404), $this->req("DELETE", "/categories/47")); $this->assertMessage(V1::respError("404", 404), $this->req("DELETE", "/categories/47"));
$this->dbMock->folderRemove->calledWith("john.doe@example.com", 46); $this->dbMock->folderRemove->calledWith("john.doe@example.com", 46);
} }
@ -497,7 +500,7 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
['id' => 2112], ['id' => 2112],
]))); ])));
$this->dbMock->subscriptionRemove->returns(true); $this->dbMock->subscriptionRemove->returns(true);
$this->assertMessage(new EmptyResponse(204), $this->req("DELETE", "/categories/1")); $this->assertMessage(HTTP::respEmpty(204), $this->req("DELETE", "/categories/1"));
Phony::inOrder( Phony::inOrder(
$this->dbMock->begin->calledWith(), $this->dbMock->begin->calledWith(),
$this->dbMock->subscriptionList->calledWith("john.doe@example.com", null, false), $this->dbMock->subscriptionList->calledWith("john.doe@example.com", null, false),
@ -510,42 +513,42 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function testListFeeds(): void { public function testListFeeds(): void {
$this->dbMock->subscriptionList->returns(new Result($this->v(self::FEEDS))); $this->dbMock->subscriptionList->returns(new Result($this->v(self::FEEDS)));
$exp = new Response(self::FEEDS_OUT); $exp = HTTP::respJson(self::FEEDS_OUT);
$this->assertMessage($exp, $this->req("GET", "/feeds")); $this->assertMessage($exp, $this->req("GET", "/feeds"));
} }
public function testListFeedsOfACategory(): void { public function testListFeedsOfACategory(): void {
$this->dbMock->subscriptionList->returns(new Result($this->v(self::FEEDS))); $this->dbMock->subscriptionList->returns(new Result($this->v(self::FEEDS)));
$exp = new Response(self::FEEDS_OUT); $exp = HTTP::respJson(self::FEEDS_OUT);
$this->assertMessage($exp, $this->req("GET", "/categories/2112/feeds")); $this->assertMessage($exp, $this->req("GET", "/categories/2112/feeds"));
$this->dbMock->subscriptionList->calledWith(Arsse::$user->id, 2111, true); $this->dbMock->subscriptionList->calledWith(Arsse::$user->id, 2111, true);
} }
public function testListFeedsOfTheRootCategory(): void { public function testListFeedsOfTheRootCategory(): void {
$this->dbMock->subscriptionList->returns(new Result($this->v(self::FEEDS))); $this->dbMock->subscriptionList->returns(new Result($this->v(self::FEEDS)));
$exp = new Response(self::FEEDS_OUT); $exp = HTTP::respJson(self::FEEDS_OUT);
$this->assertMessage($exp, $this->req("GET", "/categories/1/feeds")); $this->assertMessage($exp, $this->req("GET", "/categories/1/feeds"));
$this->dbMock->subscriptionList->calledWith(Arsse::$user->id, 0, false); $this->dbMock->subscriptionList->calledWith(Arsse::$user->id, 0, false);
} }
public function testListFeedsOfAMissingCategory(): void { public function testListFeedsOfAMissingCategory(): void {
$this->dbMock->subscriptionList->throws(new ExceptionInput("idMissing")); $this->dbMock->subscriptionList->throws(new ExceptionInput("idMissing"));
$exp = new ErrorResponse("404", 404); $exp = V1::respError("404", 404);
$this->assertMessage($exp, $this->req("GET", "/categories/2112/feeds")); $this->assertMessage($exp, $this->req("GET", "/categories/2112/feeds"));
$this->dbMock->subscriptionList->calledWith(Arsse::$user->id, 2111, true); $this->dbMock->subscriptionList->calledWith(Arsse::$user->id, 2111, true);
} }
public function testGetAFeed(): void { public function testGetAFeed(): void {
$this->dbMock->subscriptionPropertiesGet->returns($this->v(self::FEEDS[0]))->returns($this->v(self::FEEDS[1])); $this->dbMock->subscriptionPropertiesGet->returns($this->v(self::FEEDS[0]))->returns($this->v(self::FEEDS[1]));
$this->assertMessage(new Response(self::FEEDS_OUT[0]), $this->req("GET", "/feeds/1")); $this->assertMessage(HTTP::respJson(self::FEEDS_OUT[0]), $this->req("GET", "/feeds/1"));
$this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 1); $this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 1);
$this->assertMessage(new Response(self::FEEDS_OUT[1]), $this->req("GET", "/feeds/55")); $this->assertMessage(HTTP::respJson(self::FEEDS_OUT[1]), $this->req("GET", "/feeds/55"));
$this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 55); $this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 55);
} }
public function testGetAMissingFeed(): void { public function testGetAMissingFeed(): void {
$this->dbMock->subscriptionPropertiesGet->throws(new ExceptionInput("subjectMissing")); $this->dbMock->subscriptionPropertiesGet->throws(new ExceptionInput("subjectMissing"));
$this->assertMessage(new ErrorResponse("404", 404), $this->req("GET", "/feeds/1")); $this->assertMessage(V1::respError("404", 404), $this->req("GET", "/feeds/1"));
$this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 1); $this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 1);
} }
@ -611,39 +614,39 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideFeedCreations(): iterable { public function provideFeedCreations(): iterable {
self::clearData(); self::clearData();
return [ return [
[['category_id' => 1], null, null, null, null, new ErrorResponse(["MissingInputValue", 'field' => "feed_url"], 422)], [['category_id' => 1], null, null, null, null, V1::respError(["MissingInputValue", 'field' => "feed_url"], 422)],
[['feed_url' => "http://example.com/"], null, null, null, null, new ErrorResponse(["MissingInputValue", 'field' => "category_id"], 422)], [['feed_url' => "http://example.com/"], null, null, null, null, V1::respError(["MissingInputValue", 'field' => "category_id"], 422)],
[['feed_url' => "http://example.com/", 'category_id' => "1"], null, null, null, null, new ErrorResponse(["InvalidInputType", 'field' => "category_id", 'expected' => "integer", 'actual' => "string"], 422)], [['feed_url' => "http://example.com/", 'category_id' => "1"], null, null, null, null, V1::respError(["InvalidInputType", 'field' => "category_id", 'expected' => "integer", 'actual' => "string"], 422)],
[['feed_url' => "Not a URL", 'category_id' => 1], null, null, null, null, new ErrorResponse(["InvalidInputValue", 'field' => "feed_url"], 422)], [['feed_url' => "Not a URL", 'category_id' => 1], null, null, null, null, V1::respError(["InvalidInputValue", 'field' => "feed_url"], 422)],
[['feed_url' => "http://example.com/", 'category_id' => 0], null, null, null, null, new ErrorResponse(["InvalidInputValue", 'field' => "category_id"], 422)], [['feed_url' => "http://example.com/", 'category_id' => 0], null, null, null, null, V1::respError(["InvalidInputValue", 'field' => "category_id"], 422)],
[['feed_url' => "http://example.com/", 'category_id' => 1, 'keeplist_rules' => "["], null, null, null, null, new ErrorResponse(["InvalidInputValue", 'field' => "keeplist_rules"], 422)], [['feed_url' => "http://example.com/", 'category_id' => 1, 'keeplist_rules' => "["], null, null, null, null, V1::respError(["InvalidInputValue", 'field' => "keeplist_rules"], 422)],
[['feed_url' => "http://example.com/", 'category_id' => 1, 'blocklist_rules' => "["], null, null, null, null, new ErrorResponse(["InvalidInputValue", 'field' => "blocklist_rules"], 422)], [['feed_url' => "http://example.com/", 'category_id' => 1, 'blocklist_rules' => "["], null, null, null, null, V1::respError(["InvalidInputValue", 'field' => "blocklist_rules"], 422)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("internalError"), null, null, null, new ErrorResponse("FetchOther", 502)], [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("internalError"), null, null, null, V1::respError("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("invalidCertificate"), null, null, null, new ErrorResponse("FetchOther", 502)], [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("invalidCertificate"), null, null, null, V1::respError("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("invalidUrl"), null, null, null, new ErrorResponse("Fetch404", 502)], [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("invalidUrl"), null, null, null, V1::respError("Fetch404", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("maxRedirect"), null, null, null, new ErrorResponse("FetchOther", 502)], [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("maxRedirect"), null, null, null, V1::respError("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("maxSize"), null, null, null, new ErrorResponse("FetchOther", 502)], [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("maxSize"), null, null, null, V1::respError("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("timeout"), null, null, null, new ErrorResponse("FetchOther", 502)], [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("timeout"), null, null, null, V1::respError("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("forbidden"), null, null, null, new ErrorResponse("Fetch403", 502)], [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("forbidden"), null, null, null, V1::respError("Fetch403", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("unauthorized"), null, null, null, new ErrorResponse("Fetch401", 502)], [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("unauthorized"), null, null, null, V1::respError("Fetch401", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("transmissionError"), null, null, null, new ErrorResponse("FetchOther", 502)], [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("transmissionError"), null, null, null, V1::respError("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("connectionFailed"), null, null, null, new ErrorResponse("FetchOther", 502)], [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("connectionFailed"), null, null, null, V1::respError("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("malformedXml"), null, null, null, new ErrorResponse("FetchOther", 502)], [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("malformedXml"), null, null, null, V1::respError("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("xmlEntity"), null, null, null, new ErrorResponse("FetchOther", 502)], [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("xmlEntity"), null, null, null, V1::respError("FetchOther", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("subscriptionNotFound"), null, null, null, new ErrorResponse("Fetch404", 502)], [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("subscriptionNotFound"), null, null, null, V1::respError("Fetch404", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("unsupportedFeedFormat"), null, null, null, new ErrorResponse("FetchFormat", 502)], [['feed_url' => "http://example.com/", 'category_id' => 1], new FeedException("unsupportedFeedFormat"), null, null, null, V1::respError("FetchFormat", 502)],
[['feed_url' => "http://example.com/", 'category_id' => 1], 2112, new ExceptionInput("constraintViolation"), null, null, new ErrorResponse("DuplicateFeed", 409)], [['feed_url' => "http://example.com/", 'category_id' => 1], 2112, new ExceptionInput("constraintViolation"), null, null, V1::respError("DuplicateFeed", 409)],
[['feed_url' => "http://example.com/", 'category_id' => 1], 2112, 44, new ExceptionInput("idMissing"), null, new ErrorResponse("MissingCategory", 422)], [['feed_url' => "http://example.com/", 'category_id' => 1], 2112, 44, new ExceptionInput("idMissing"), null, V1::respError("MissingCategory", 422)],
[['feed_url' => "http://example.com/", 'category_id' => 1], 2112, 44, true, null, new Response(['feed_id' => 44], 201)], [['feed_url' => "http://example.com/", 'category_id' => 1], 2112, 44, true, null, HTTP::respJson(['feed_id' => 44], 201)],
[['feed_url' => "http://example.com/", 'category_id' => 1, 'keeplist_rules' => "^A"], 2112, 44, true, true, new Response(['feed_id' => 44], 201)], [['feed_url' => "http://example.com/", 'category_id' => 1, 'keeplist_rules' => "^A"], 2112, 44, true, true, HTTP::respJson(['feed_id' => 44], 201)],
[['feed_url' => "http://example.com/", 'category_id' => 1, 'blocklist_rules' => "A"], 2112, 44, true, true, new Response(['feed_id' => 44], 201)], [['feed_url' => "http://example.com/", 'category_id' => 1, 'blocklist_rules' => "A"], 2112, 44, true, true, HTTP::respJson(['feed_id' => 44], 201)],
]; ];
} }
/** @dataProvider provideFeedModifications */ /** @dataProvider provideFeedModifications */
public function testModifyAFeed(array $in, array $data, $out, ResponseInterface $exp): void { public function testModifyAFeed(array $in, array $data, $out, ResponseInterface $exp): void {
$this->h = $this->partialMock(V1::class); $this->h = $this->partialMock(V1::class);
$this->h->getFeed->returns(new Response(self::FEEDS_OUT[0])); $this->h->getFeed->returns(HTTP::respJson(self::FEEDS_OUT[0]));
if ($out instanceof \Exception) { if ($out instanceof \Exception) {
$this->dbMock->subscriptionPropertiesSet->throws($out); $this->dbMock->subscriptionPropertiesSet->throws($out);
} else { } else {
@ -655,14 +658,14 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideFeedModifications(): iterable { public function provideFeedModifications(): iterable {
self::clearData(); self::clearData();
$success = new Response(self::FEEDS_OUT[0], 201); $success = HTTP::respJson(self::FEEDS_OUT[0], 201);
return [ return [
[[], [], true, $success], [[], [], true, $success],
[[], [], new ExceptionInput("subjectMissing"), new ErrorResponse("404", 404)], [[], [], new ExceptionInput("subjectMissing"), V1::respError("404", 404)],
[['title' => ""], ['title' => ""], new ExceptionInput("missing"), new ErrorResponse("InvalidTitle", 422)], [['title' => ""], ['title' => ""], new ExceptionInput("missing"), V1::respError("InvalidTitle", 422)],
[['title' => " "], ['title' => " "], new ExceptionInput("whitespace"), new ErrorResponse("InvalidTitle", 422)], [['title' => " "], ['title' => " "], new ExceptionInput("whitespace"), V1::respError("InvalidTitle", 422)],
[['title' => " "], ['title' => " "], new ExceptionInput("whitespace"), new ErrorResponse("InvalidTitle", 422)], [['title' => " "], ['title' => " "], new ExceptionInput("whitespace"), V1::respError("InvalidTitle", 422)],
[['category_id' => 47], ['folder' => 46], new ExceptionInput("idMissing"), new ErrorResponse("MissingCategory", 422)], [['category_id' => 47], ['folder' => 46], new ExceptionInput("idMissing"), V1::respError("MissingCategory", 422)],
[['crawler' => false], ['scrape' => false], true, $success], [['crawler' => false], ['scrape' => false], true, $success],
[['keeplist_rules' => ""], ['keep_rule' => ""], true, $success], [['keeplist_rules' => ""], ['keep_rule' => ""], true, $success],
[['blocklist_rules' => "ook"], ['block_rule' => "ook"], true, $success], [['blocklist_rules' => "ook"], ['block_rule' => "ook"], true, $success],
@ -672,21 +675,21 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function testModifyAFeedWithNoBody(): void { public function testModifyAFeedWithNoBody(): void {
$this->h = $this->partialMock(V1::class); $this->h = $this->partialMock(V1::class);
$this->h->getFeed->returns(new Response(self::FEEDS_OUT[0])); $this->h->getFeed->returns(HTTP::respJson(self::FEEDS_OUT[0]));
$this->dbMock->subscriptionPropertiesSet->returns(true); $this->dbMock->subscriptionPropertiesSet->returns(true);
$this->assertMessage(new Response(self::FEEDS_OUT[0], 201), $this->req("PUT", "/feeds/2112", "")); $this->assertMessage(HTTP::respJson(self::FEEDS_OUT[0], 201), $this->req("PUT", "/feeds/2112", ""));
$this->dbMock->subscriptionPropertiesSet->calledWith(Arsse::$user->id, 2112, []); $this->dbMock->subscriptionPropertiesSet->calledWith(Arsse::$user->id, 2112, []);
} }
public function testDeleteAFeed(): void { public function testDeleteAFeed(): void {
$this->dbMock->subscriptionRemove->returns(true); $this->dbMock->subscriptionRemove->returns(true);
$this->assertMessage(new EmptyResponse(204), $this->req("DELETE", "/feeds/2112")); $this->assertMessage(HTTP::respEmpty(204), $this->req("DELETE", "/feeds/2112"));
$this->dbMock->subscriptionRemove->calledWith(Arsse::$user->id, 2112); $this->dbMock->subscriptionRemove->calledWith(Arsse::$user->id, 2112);
} }
public function testDeleteAMissingFeed(): void { public function testDeleteAMissingFeed(): void {
$this->dbMock->subscriptionRemove->throws(new ExceptionInput("subjectMissing")); $this->dbMock->subscriptionRemove->throws(new ExceptionInput("subjectMissing"));
$this->assertMessage(new ErrorResponse("404", 404), $this->req("DELETE", "/feeds/2112")); $this->assertMessage(V1::respError("404", 404), $this->req("DELETE", "/feeds/2112"));
$this->dbMock->subscriptionRemove->calledWith(Arsse::$user->id, 2112); $this->dbMock->subscriptionRemove->calledWith(Arsse::$user->id, 2112);
} }
@ -703,12 +706,12 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideIcons(): iterable { public function provideIcons(): iterable {
return [ return [
[['id' => 44, 'type' => "image/svg+xml", 'data' => "<svg/>"], new Response(['id' => 44, 'data' => "image/svg+xml;base64,PHN2Zy8+", 'mime_type' => "image/svg+xml"])], [['id' => 44, 'type' => "image/svg+xml", 'data' => "<svg/>"], HTTP::respJson(['id' => 44, 'data' => "image/svg+xml;base64,PHN2Zy8+", 'mime_type' => "image/svg+xml"])],
[['id' => 47, 'type' => "", 'data' => "<svg/>"], new ErrorResponse("404", 404)], [['id' => 47, 'type' => "", 'data' => "<svg/>"], V1::respError("404", 404)],
[['id' => 47, 'type' => null, 'data' => "<svg/>"], new ErrorResponse("404", 404)], [['id' => 47, 'type' => null, 'data' => "<svg/>"], V1::respError("404", 404)],
[['id' => 47, 'type' => null, 'data' => null], new ErrorResponse("404", 404)], [['id' => 47, 'type' => null, 'data' => null], V1::respError("404", 404)],
[null, new ErrorResponse("404", 404)], [null, V1::respError("404", 404)],
[new ExceptionInput("subjectMissing"), new ErrorResponse("404", 404)], [new ExceptionInput("subjectMissing"), V1::respError("404", 404)],
]; ];
} }
@ -744,62 +747,62 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
$c = (new Context)->limit(100); $c = (new Context)->limit(100);
$o = ["modified_date"]; // the default sort order $o = ["modified_date"]; // the default sort order
return [ return [
["/entries?after=A", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "after"], 400)], ["/entries?after=A", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "after"], 400)],
["/entries?before=B", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "before"], 400)], ["/entries?before=B", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "before"], 400)],
["/entries?category_id=0", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "category_id"], 400)], ["/entries?category_id=0", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "category_id"], 400)],
["/entries?after_entry_id=0", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "after_entry_id"], 400)], ["/entries?after_entry_id=0", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "after_entry_id"], 400)],
["/entries?before_entry_id=0", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "before_entry_id"], 400)], ["/entries?before_entry_id=0", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "before_entry_id"], 400)],
["/entries?limit=-1", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "limit"], 400)], ["/entries?limit=-1", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "limit"], 400)],
["/entries?offset=-1", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "offset"], 400)], ["/entries?offset=-1", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "offset"], 400)],
["/entries?direction=sideways", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "direction"], 400)], ["/entries?direction=sideways", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "direction"], 400)],
["/entries?order=false", null, null, [], false, new ErrorResponse(["InvalidInputValue", 'field' => "order"], 400)], ["/entries?order=false", null, null, [], false, V1::respError(["InvalidInputValue", 'field' => "order"], 400)],
["/entries?starred&starred", null, null, [], false, new ErrorResponse(["DuplicateInputValue", 'field' => "starred"], 400)], ["/entries?starred&starred", null, null, [], false, V1::respError(["DuplicateInputValue", 'field' => "starred"], 400)],
["/entries?after&after=0", null, null, [], false, new ErrorResponse(["DuplicateInputValue", 'field' => "after"], 400)], ["/entries?after&after=0", null, null, [], false, V1::respError(["DuplicateInputValue", 'field' => "after"], 400)],
["/entries", $c, $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries", $c, $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?category_id=47", (clone $c)->folder(46), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?category_id=47", (clone $c)->folder(46), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?category_id=1", (clone $c)->folderShallow(0), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?category_id=1", (clone $c)->folderShallow(0), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?status=unread", (clone $c)->unread(true)->hidden(false), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?status=unread", (clone $c)->unread(true)->hidden(false), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?status=read", (clone $c)->unread(false)->hidden(false), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?status=read", (clone $c)->unread(false)->hidden(false), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?status=removed", (clone $c)->hidden(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?status=removed", (clone $c)->hidden(true), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?status=unread&status=read", (clone $c)->hidden(false), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?status=unread&status=read", (clone $c)->hidden(false), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?status=unread&status=removed", new UnionContext((clone $c)->unread(true), (clone $c)->hidden(true)), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?status=unread&status=removed", new UnionContext((clone $c)->unread(true), (clone $c)->hidden(true)), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?status=removed&status=read", new UnionContext((clone $c)->unread(false), (clone $c)->hidden(true)), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?status=removed&status=read", new UnionContext((clone $c)->unread(false), (clone $c)->hidden(true)), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?status=removed&status=read&status=removed", new UnionContext((clone $c)->unread(false), (clone $c)->hidden(true)), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?status=removed&status=read&status=removed", new UnionContext((clone $c)->unread(false), (clone $c)->hidden(true)), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?status=removed&status=read&status=unread", $c, $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?status=removed&status=read&status=unread", $c, $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?starred", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?starred", (clone $c)->starred(true), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?starred=", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?starred=", (clone $c)->starred(true), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?starred=true", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?starred=true", (clone $c)->starred(true), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?starred=false", (clone $c)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?starred=false", (clone $c)->starred(true), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?after=0", (clone $c)->modifiedRange(0, null), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?after=0", (clone $c)->modifiedRange(0, null), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?before=0", $c, $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?before=0", $c, $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?before=1", (clone $c)->modifiedRange(null, 1), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?before=1", (clone $c)->modifiedRange(null, 1), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?before=1&after=0", (clone $c)->modifiedRange(0, 1), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?before=1&after=0", (clone $c)->modifiedRange(0, 1), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?after_entry_id=42", (clone $c)->articleRange(43, null), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?after_entry_id=42", (clone $c)->articleRange(43, null), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?before_entry_id=47", (clone $c)->articleRange(null, 46), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?before_entry_id=47", (clone $c)->articleRange(null, 46), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?search=alpha%20beta", (clone $c)->searchTerms(["alpha", "beta"]), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?search=alpha%20beta", (clone $c)->searchTerms(["alpha", "beta"]), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?limit=4", (clone $c)->limit(4), $o, self::ENTRIES, true, new Response(['total' => 2112, 'entries' => self::ENTRIES_OUT])], ["/entries?limit=4", (clone $c)->limit(4), $o, self::ENTRIES, true, HTTP::respJson(['total' => 2112, 'entries' => self::ENTRIES_OUT])],
["/entries?offset=20", (clone $c)->offset(20), $o, [], true, new Response(['total' => 2112, 'entries' => []])], ["/entries?offset=20", (clone $c)->offset(20), $o, [], true, HTTP::respJson(['total' => 2112, 'entries' => []])],
["/entries?direction=asc", $c, $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?direction=asc", $c, $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?order=id", $c, ["id"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?order=id", $c, ["id"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?order=published_at", $c, ["modified_date"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?order=published_at", $c, ["modified_date"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?order=category_id", $c, ["top_folder"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?order=category_id", $c, ["top_folder"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?order=category_title", $c, ["top_folder_name"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?order=category_title", $c, ["top_folder_name"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?order=status", $c, ["hidden", "unread desc"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?order=status", $c, ["hidden", "unread desc"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?direction=desc", $c, ["modified_date desc"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?direction=desc", $c, ["modified_date desc"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?order=id&direction=desc", $c, ["id desc"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?order=id&direction=desc", $c, ["id desc"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?order=published_at&direction=desc", $c, ["modified_date desc"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?order=published_at&direction=desc", $c, ["modified_date desc"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?order=category_id&direction=desc", $c, ["top_folder desc"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?order=category_id&direction=desc", $c, ["top_folder desc"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?order=category_title&direction=desc", $c, ["top_folder_name desc"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?order=category_title&direction=desc", $c, ["top_folder_name desc"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?order=status&direction=desc", $c, ["hidden desc", "unread"], self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/entries?order=status&direction=desc", $c, ["hidden desc", "unread"], self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/entries?category_id=2112", (clone $c)->folder(2111), $o, new ExceptionInput("idMissing"), false, new ErrorResponse("MissingCategory")], ["/entries?category_id=2112", (clone $c)->folder(2111), $o, new ExceptionInput("idMissing"), false, V1::respError("MissingCategory")],
["/feeds/42/entries", (clone $c)->subscription(42), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/feeds/42/entries", (clone $c)->subscription(42), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/feeds/42/entries?category_id=47", (clone $c)->subscription(42)->folder(46), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/feeds/42/entries?category_id=47", (clone $c)->subscription(42)->folder(46), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/feeds/2112/entries", (clone $c)->subscription(2112), $o, new ExceptionInput("idMissing"), false, new ErrorResponse("404", 404)], ["/feeds/2112/entries", (clone $c)->subscription(2112), $o, new ExceptionInput("idMissing"), false, V1::respError("404", 404)],
["/categories/42/entries", (clone $c)->folder(41), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/categories/42/entries", (clone $c)->folder(41), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/categories/42/entries?category_id=47", (clone $c)->folder(41), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/categories/42/entries?category_id=47", (clone $c)->folder(41), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/categories/42/entries?starred", (clone $c)->folder(41)->starred(true), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/categories/42/entries?starred", (clone $c)->folder(41)->starred(true), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/categories/1/entries", (clone $c)->folderShallow(0), $o, self::ENTRIES, false, new Response(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])], ["/categories/1/entries", (clone $c)->folderShallow(0), $o, self::ENTRIES, false, HTTP::respJson(['total' => sizeof(self::ENTRIES_OUT), 'entries' => self::ENTRIES_OUT])],
["/categories/2112/entries", (clone $c)->folder(2111), $o, new ExceptionInput("idMissing"), false, new ErrorResponse("404", 404)], ["/categories/2112/entries", (clone $c)->folder(2111), $o, new ExceptionInput("idMissing"), false, V1::respError("404", 404)],
]; ];
} }
@ -828,17 +831,17 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
self::clearData(); self::clearData();
$c = new Context; $c = new Context;
return [ return [
["/entries/42", (clone $c)->article(42), [self::ENTRIES[1]], new Response(self::ENTRIES_OUT[1])], ["/entries/42", (clone $c)->article(42), [self::ENTRIES[1]], HTTP::respJson(self::ENTRIES_OUT[1])],
["/entries/2112", (clone $c)->article(2112), new ExceptionInput("subjectMissing"), new ErrorResponse("404", 404)], ["/entries/2112", (clone $c)->article(2112), new ExceptionInput("subjectMissing"), V1::respError("404", 404)],
["/feeds/47/entries/42", (clone $c)->subscription(47)->article(42), [self::ENTRIES[1]], new Response(self::ENTRIES_OUT[1])], ["/feeds/47/entries/42", (clone $c)->subscription(47)->article(42), [self::ENTRIES[1]], HTTP::respJson(self::ENTRIES_OUT[1])],
["/feeds/47/entries/44", (clone $c)->subscription(47)->article(44), [], new ErrorResponse("404", 404)], ["/feeds/47/entries/44", (clone $c)->subscription(47)->article(44), [], V1::respError("404", 404)],
["/feeds/47/entries/2112", (clone $c)->subscription(47)->article(2112), new ExceptionInput("subjectMissing"), new ErrorResponse("404", 404)], ["/feeds/47/entries/2112", (clone $c)->subscription(47)->article(2112), new ExceptionInput("subjectMissing"), V1::respError("404", 404)],
["/feeds/2112/entries/47", (clone $c)->subscription(2112)->article(47), new ExceptionInput("idMissing"), new ErrorResponse("404", 404)], ["/feeds/2112/entries/47", (clone $c)->subscription(2112)->article(47), new ExceptionInput("idMissing"), V1::respError("404", 404)],
["/categories/47/entries/42", (clone $c)->folder(46)->article(42), [self::ENTRIES[1]], new Response(self::ENTRIES_OUT[1])], ["/categories/47/entries/42", (clone $c)->folder(46)->article(42), [self::ENTRIES[1]], HTTP::respJson(self::ENTRIES_OUT[1])],
["/categories/47/entries/44", (clone $c)->folder(46)->article(44), [], new ErrorResponse("404", 404)], ["/categories/47/entries/44", (clone $c)->folder(46)->article(44), [], V1::respError("404", 404)],
["/categories/47/entries/2112", (clone $c)->folder(46)->article(2112), new ExceptionInput("subjectMissing"), new ErrorResponse("404", 404)], ["/categories/47/entries/2112", (clone $c)->folder(46)->article(2112), new ExceptionInput("subjectMissing"), V1::respError("404", 404)],
["/categories/2112/entries/47", (clone $c)->folder(2111)->article(47), new ExceptionInput("idMissing"), new ErrorResponse("404", 404)], ["/categories/2112/entries/47", (clone $c)->folder(2111)->article(47), new ExceptionInput("idMissing"), V1::respError("404", 404)],
["/categories/1/entries/42", (clone $c)->folderShallow(0)->article(42), [self::ENTRIES[1]], new Response(self::ENTRIES_OUT[1])], ["/categories/1/entries/42", (clone $c)->folderShallow(0)->article(42), [self::ENTRIES[1]], HTTP::respJson(self::ENTRIES_OUT[1])],
]; ];
} }
@ -856,17 +859,17 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideEntryMarkings(): iterable { public function provideEntryMarkings(): iterable {
self::clearData(); self::clearData();
return [ return [
[['status' => "read"], null, new ErrorResponse(["MissingInputValue", 'field' => "entry_ids"], 422)], [['status' => "read"], null, V1::respError(["MissingInputValue", 'field' => "entry_ids"], 422)],
[['entry_ids' => [1]], null, new ErrorResponse(["MissingInputValue", 'field' => "status"], 422)], [['entry_ids' => [1]], null, V1::respError(["MissingInputValue", 'field' => "status"], 422)],
[['entry_ids' => [], 'status' => "read"], null, new ErrorResponse(["MissingInputValue", 'field' => "entry_ids"], 422)], [['entry_ids' => [], 'status' => "read"], null, V1::respError(["MissingInputValue", 'field' => "entry_ids"], 422)],
[['entry_ids' => 1, 'status' => "read"], null, new ErrorResponse(["InvalidInputType", 'field' => "entry_ids", 'expected' => "array", 'actual' => "integer"], 422)], [['entry_ids' => 1, 'status' => "read"], null, V1::respError(["InvalidInputType", 'field' => "entry_ids", 'expected' => "array", 'actual' => "integer"], 422)],
[['entry_ids' => ["1"], 'status' => "read"], null, new ErrorResponse(["InvalidInputType", 'field' => "entry_ids", 'expected' => "integer", 'actual' => "string"], 422)], [['entry_ids' => ["1"], 'status' => "read"], null, V1::respError(["InvalidInputType", 'field' => "entry_ids", 'expected' => "integer", 'actual' => "string"], 422)],
[['entry_ids' => [1], 'status' => 1], null, new ErrorResponse(["InvalidInputType", 'field' => "status", 'expected' => "string", 'actual' => "integer"], 422)], [['entry_ids' => [1], 'status' => 1], null, V1::respError(["InvalidInputType", 'field' => "status", 'expected' => "string", 'actual' => "integer"], 422)],
[['entry_ids' => [0], 'status' => "read"], null, new ErrorResponse(["InvalidInputValue", 'field' => "entry_ids"], 422)], [['entry_ids' => [0], 'status' => "read"], null, V1::respError(["InvalidInputValue", 'field' => "entry_ids"], 422)],
[['entry_ids' => [1], 'status' => "reread"], null, new ErrorResponse(["InvalidInputValue", 'field' => "status"], 422)], [['entry_ids' => [1], 'status' => "reread"], null, V1::respError(["InvalidInputValue", 'field' => "status"], 422)],
[['entry_ids' => [1, 2], 'status' => "read"], ['read' => true, 'hidden' => false], new EmptyResponse(204)], [['entry_ids' => [1, 2], 'status' => "read"], ['read' => true, 'hidden' => false], HTTP::respEmpty(204)],
[['entry_ids' => [1, 2], 'status' => "unread"], ['read' => false, 'hidden' => false], new EmptyResponse(204)], [['entry_ids' => [1, 2], 'status' => "unread"], ['read' => false, 'hidden' => false], HTTP::respEmpty(204)],
[['entry_ids' => [1, 2], 'status' => "removed"], ['read' => true, 'hidden' => true], new EmptyResponse(204)], [['entry_ids' => [1, 2], 'status' => "removed"], ['read' => true, 'hidden' => true], HTTP::respEmpty(204)],
]; ];
} }
@ -889,13 +892,13 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
self::clearData(); self::clearData();
$c = (new Context)->hidden(false); $c = (new Context)->hidden(false);
return [ return [
["/users/42/mark-all-as-read", $c, 1123, new EmptyResponse(204)], ["/users/42/mark-all-as-read", $c, 1123, HTTP::respEmpty(204)],
["/users/2112/mark-all-as-read", $c, null, new ErrorResponse("403", 403)], ["/users/2112/mark-all-as-read", $c, null, V1::respError("403", 403)],
["/feeds/47/mark-all-as-read", (clone $c)->subscription(47), 2112, new EmptyResponse(204)], ["/feeds/47/mark-all-as-read", (clone $c)->subscription(47), 2112, HTTP::respEmpty(204)],
["/feeds/2112/mark-all-as-read", (clone $c)->subscription(2112), new ExceptionInput("idMissing"), new ErrorResponse("404", 404)], ["/feeds/2112/mark-all-as-read", (clone $c)->subscription(2112), new ExceptionInput("idMissing"), V1::respError("404", 404)],
["/categories/47/mark-all-as-read", (clone $c)->folder(46), 1337, new EmptyResponse(204)], ["/categories/47/mark-all-as-read", (clone $c)->folder(46), 1337, HTTP::respEmpty(204)],
["/categories/2112/mark-all-as-read", (clone $c)->folder(2111), new ExceptionInput("idMissing"), new ErrorResponse("404", 404)], ["/categories/2112/mark-all-as-read", (clone $c)->folder(2111), new ExceptionInput("idMissing"), V1::respError("404", 404)],
["/categories/1/mark-all-as-read", (clone $c)->folderShallow(0), 6666, new EmptyResponse(204)], ["/categories/1/mark-all-as-read", (clone $c)->folderShallow(0), 6666, HTTP::respEmpty(204)],
]; ];
} }
@ -929,26 +932,26 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideBookmarkTogglings(): iterable { public function provideBookmarkTogglings(): iterable {
self::clearData(); self::clearData();
return [ return [
[1, true, new EmptyResponse(204)], [1, true, HTTP::respEmpty(204)],
[0, false, new EmptyResponse(204)], [0, false, HTTP::respEmpty(204)],
[new ExceptionInput("subjectMissing"), null, new ErrorResponse("404", 404)], [new ExceptionInput("subjectMissing"), null, V1::respError("404", 404)],
]; ];
} }
public function testRefreshAFeed(): void { public function testRefreshAFeed(): void {
$this->dbMock->subscriptionPropertiesGet->returns([]); $this->dbMock->subscriptionPropertiesGet->returns([]);
$this->assertMessage(new EmptyResponse(204), $this->req("PUT", "/feeds/47/refresh")); $this->assertMessage(HTTP::respEmpty(204), $this->req("PUT", "/feeds/47/refresh"));
$this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 47); $this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 47);
} }
public function testRefreshAMissingFeed(): void { public function testRefreshAMissingFeed(): void {
$this->dbMock->subscriptionPropertiesGet->throws(new ExceptionInput("subjectMissing")); $this->dbMock->subscriptionPropertiesGet->throws(new ExceptionInput("subjectMissing"));
$this->assertMessage(new ErrorResponse("404", 404), $this->req("PUT", "/feeds/2112/refresh")); $this->assertMessage(V1::respError("404", 404), $this->req("PUT", "/feeds/2112/refresh"));
$this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 2112); $this->dbMock->subscriptionPropertiesGet->calledWith(Arsse::$user->id, 2112);
} }
public function testRefreshAllFeeds(): void { public function testRefreshAllFeeds(): void {
$this->assertMessage(new EmptyResponse(204), $this->req("PUT", "/feeds/refresh")); $this->assertMessage(HTTP::respEmpty(204), $this->req("PUT", "/feeds/refresh"));
} }
/** @dataProvider provideImports */ /** @dataProvider provideImports */
@ -964,21 +967,21 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideImports(): iterable { public function provideImports(): iterable {
self::clearData(); self::clearData();
return [ return [
[new ImportException("invalidSyntax"), new ErrorResponse("InvalidBodyXML", 400)], [new ImportException("invalidSyntax"), V1::respError("InvalidBodyXML", 400)],
[new ImportException("invalidSemantics"), new ErrorResponse("InvalidBodyOPML", 422)], [new ImportException("invalidSemantics"), V1::respError("InvalidBodyOPML", 422)],
[new ImportException("invalidFolderName"), new ErrorResponse("InvalidImportCategory", 422)], [new ImportException("invalidFolderName"), V1::respError("InvalidImportCategory", 422)],
[new ImportException("invalidFolderCopy"), new ErrorResponse("DuplicateImportCategory", 422)], [new ImportException("invalidFolderCopy"), V1::respError("DuplicateImportCategory", 422)],
[new ImportException("invalidTagName"), new ErrorResponse("InvalidImportLabel", 422)], [new ImportException("invalidTagName"), V1::respError("InvalidImportLabel", 422)],
[new FeedException("invalidUrl", ['url' => "http://example.com/"]), new ErrorResponse(["FailedImportFeed", 'url' => "http://example.com/", 'code' => 10502], 502)], [new FeedException("invalidUrl", ['url' => "http://example.com/"]), V1::respError(["FailedImportFeed", 'url' => "http://example.com/", 'code' => 10502], 502)],
[true, new Response(['message' => Arsse::$lang->msg("API.Miniflux.ImportSuccess")])], [true, HTTP::respJson(['message' => Arsse::$lang->msg("API.Miniflux.ImportSuccess")])],
]; ];
} }
public function testExport(): void { public function testExport(): void {
$opml = $this->mock(OPML::class); $opml = $this->mock(OPML::class);
$this->objMock->get->with(OPML::class)->returns($opml); $this->objMock->get->with(OPML::class)->returns($opml);
$opml->export->returns("EXPORT DATA"); $opml->export->returns("<EXPORT_DATA/>");
$this->assertMessage(new TextResponse("EXPORT DATA", 200, ['Content-Type' => "application/xml"]), $this->req("GET", "/export")); $this->assertMessage(HTTP::respText("<EXPORT_DATA/>", 200, ['Content-Type' => "application/xml"]), $this->req("GET", "/export"));
$opml->export->calledWith(Arsse::$user->id); $opml->export->calledWith(Arsse::$user->id);
} }
} }

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

@ -11,13 +11,12 @@ use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\Test\Result; use JKingWeb\Arsse\Test\Result;
use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Context\Context;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\Transaction; use JKingWeb\Arsse\Db\Transaction;
use JKingWeb\Arsse\REST\NextcloudNews\V1_2; use JKingWeb\Arsse\REST\NextcloudNews\V1_2;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse;
/** @covers \JKingWeb\Arsse\REST\NextcloudNews\V1_2<extended> */ /** @covers \JKingWeb\Arsse\REST\NextcloudNews\V1_2<extended> */
class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
@ -336,13 +335,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
} }
public function testSendAuthenticationChallenge(): void { public function testSendAuthenticationChallenge(): void {
$exp = new EmptyResponse(401); $exp = HTTP::respEmpty(401);
$this->assertMessage($exp, $this->req("GET", "/", "", [], false)); $this->assertMessage($exp, $this->req("GET", "/", "", [], false));
} }
/** @dataProvider provideInvalidPaths */ /** @dataProvider provideInvalidPaths */
public function testRespondToInvalidPaths($path, $method, $code, $allow = null): void { public function testRespondToInvalidPaths($path, $method, $code, $allow = null): void {
$exp = new EmptyResponse($code, $allow ? ['Allow' => $allow] : []); $exp = HTTP::respEmpty($code, $allow ? ['Allow' => $allow] : []);
$this->assertMessage($exp, $this->req($method, $path)); $this->assertMessage($exp, $this->req($method, $path));
} }
@ -374,16 +373,16 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
} }
public function testRespondToInvalidInputTypes(): void { public function testRespondToInvalidInputTypes(): void {
$exp = new EmptyResponse(415, ['Accept' => "application/json"]); $exp = HTTP::respEmpty(415, ['Accept' => "application/json"]);
$this->assertMessage($exp, $this->req("PUT", "/folders/1", '<data/>', ['Content-Type' => "application/xml"])); $this->assertMessage($exp, $this->req("PUT", "/folders/1", '<data/>', ['Content-Type' => "application/xml"]));
$exp = new EmptyResponse(400); $exp = HTTP::respEmpty(400);
$this->assertMessage($exp, $this->req("PUT", "/folders/1", '<data/>')); $this->assertMessage($exp, $this->req("PUT", "/folders/1", '<data/>'));
$this->assertMessage($exp, $this->req("PUT", "/folders/1", '<data/>', ['Content-Type' => null])); $this->assertMessage($exp, $this->req("PUT", "/folders/1", '<data/>', ['Content-Type' => null]));
} }
/** @dataProvider provideOptionsRequests */ /** @dataProvider provideOptionsRequests */
public function testRespondToOptionsRequests(string $url, string $allow, string $accept): void { public function testRespondToOptionsRequests(string $url, string $allow, string $accept): void {
$exp = new EmptyResponse(204, [ $exp = HTTP::respEmpty(204, [
'Allow' => $allow, 'Allow' => $allow,
'Accept' => $accept, 'Accept' => $accept,
]); ]);
@ -408,7 +407,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
['id' => 12, 'name' => "Hardware"], ['id' => 12, 'name' => "Hardware"],
]; ];
$this->dbMock->folderList->with($this->userId, null, false)->returns(new Result($this->v($list))); $this->dbMock->folderList->with($this->userId, null, false)->returns(new Result($this->v($list)));
$exp = new Response(['folders' => $out]); $exp = HTTP::respJson(['folders' => $out]);
$this->assertMessage($exp, $this->req("GET", "/folders")); $this->assertMessage($exp, $this->req("GET", "/folders"));
} }
@ -432,23 +431,23 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideFolderCreations(): array { public function provideFolderCreations(): array {
return [ return [
[['name' => "Software"], true, 1, new Response(['folders' => [['id' => 1, 'name' => "Software"]]])], [['name' => "Software"], true, 1, HTTP::respJson(['folders' => [['id' => 1, 'name' => "Software"]]])],
[['name' => "Software"], false, 1, new Response(['folders' => [['id' => 1, 'name' => "Software"]]])], [['name' => "Software"], false, 1, HTTP::respJson(['folders' => [['id' => 1, 'name' => "Software"]]])],
[['name' => "Hardware"], true, "2", new Response(['folders' => [['id' => 2, 'name' => "Hardware"]]])], [['name' => "Hardware"], true, "2", HTTP::respJson(['folders' => [['id' => 2, 'name' => "Hardware"]]])],
[['name' => "Hardware"], false, "2", new Response(['folders' => [['id' => 2, 'name' => "Hardware"]]])], [['name' => "Hardware"], false, "2", HTTP::respJson(['folders' => [['id' => 2, 'name' => "Hardware"]]])],
[['name' => "Software"], true, new ExceptionInput("constraintViolation"), new EmptyResponse(409)], [['name' => "Software"], true, new ExceptionInput("constraintViolation"), HTTP::respEmpty(409)],
[['name' => ""], true, new ExceptionInput("whitespace"), new EmptyResponse(422)], [['name' => ""], true, new ExceptionInput("whitespace"), HTTP::respEmpty(422)],
[['name' => " "], true, new ExceptionInput("whitespace"), new EmptyResponse(422)], [['name' => " "], true, new ExceptionInput("whitespace"), HTTP::respEmpty(422)],
[['name' => null], true, new ExceptionInput("missing"), new EmptyResponse(422)], [['name' => null], true, new ExceptionInput("missing"), HTTP::respEmpty(422)],
]; ];
} }
public function testRemoveAFolder(): void { public function testRemoveAFolder(): void {
$this->dbMock->folderRemove->with($this->userId, 1)->returns(true)->throws(new ExceptionInput("subjectMissing")); $this->dbMock->folderRemove->with($this->userId, 1)->returns(true)->throws(new ExceptionInput("subjectMissing"));
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("DELETE", "/folders/1")); $this->assertMessage($exp, $this->req("DELETE", "/folders/1"));
// fail on the second invocation because it no longer exists // fail on the second invocation because it no longer exists
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("DELETE", "/folders/1")); $this->assertMessage($exp, $this->req("DELETE", "/folders/1"));
$this->dbMock->folderRemove->times(2)->calledWith($this->userId, 1); $this->dbMock->folderRemove->times(2)->calledWith($this->userId, 1);
} }
@ -467,17 +466,17 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideFolderRenamings(): array { public function provideFolderRenamings(): array {
return [ return [
[['name' => "Software"], 1, true, new EmptyResponse(204)], [['name' => "Software"], 1, true, HTTP::respEmpty(204)],
[['name' => "Software"], 2, new ExceptionInput("constraintViolation"), new EmptyResponse(409)], [['name' => "Software"], 2, new ExceptionInput("constraintViolation"), HTTP::respEmpty(409)],
[['name' => "Software"], 3, new ExceptionInput("subjectMissing"), new EmptyResponse(404)], [['name' => "Software"], 3, new ExceptionInput("subjectMissing"), HTTP::respEmpty(404)],
[['name' => ""], 2, new ExceptionInput("whitespace"), new EmptyResponse(422)], [['name' => ""], 2, new ExceptionInput("whitespace"), HTTP::respEmpty(422)],
[['name' => " "], 2, new ExceptionInput("whitespace"), new EmptyResponse(422)], [['name' => " "], 2, new ExceptionInput("whitespace"), HTTP::respEmpty(422)],
[['name' => null], 2, new ExceptionInput("missing"), new EmptyResponse(422)], [['name' => null], 2, new ExceptionInput("missing"), HTTP::respEmpty(422)],
]; ];
} }
public function testRetrieveServerVersion(): void { public function testRetrieveServerVersion(): void {
$exp = new Response([ $exp = HTTP::respJson([
'version' => V1_2::VERSION, 'version' => V1_2::VERSION,
'arsse_version' => Arsse::VERSION, 'arsse_version' => Arsse::VERSION,
]); ]);
@ -497,9 +496,9 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->subscriptionList->with($this->userId)->returns(new Result([]))->returns(new Result($this->v($this->feeds['db']))); $this->dbMock->subscriptionList->with($this->userId)->returns(new Result([]))->returns(new Result($this->v($this->feeds['db'])));
$this->dbMock->articleStarred->with($this->userId)->returns($this->v(['total' => 0]))->returns($this->v(['total' => 5])); $this->dbMock->articleStarred->with($this->userId)->returns($this->v(['total' => 0]))->returns($this->v(['total' => 5]));
$this->dbMock->editionLatest->with($this->userId)->returns(0)->returns(4758915); $this->dbMock->editionLatest->with($this->userId)->returns(0)->returns(4758915);
$exp = new Response($exp1); $exp = HTTP::respJson($exp1);
$this->assertMessage($exp, $this->req("GET", "/feeds")); $this->assertMessage($exp, $this->req("GET", "/feeds"));
$exp = new Response($exp2); $exp = HTTP::respJson($exp2);
$this->assertMessage($exp, $this->req("GET", "/feeds")); $this->assertMessage($exp, $this->req("GET", "/feeds"));
} }
@ -538,21 +537,21 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideNewSubscriptions(): array { public function provideNewSubscriptions(): array {
$feedException = new \JKingWeb\Arsse\Feed\Exception("", [], new \PicoFeed\Reader\SubscriptionNotFoundException); $feedException = new \JKingWeb\Arsse\Feed\Exception("", [], new \PicoFeed\Reader\SubscriptionNotFoundException);
return [ return [
[['url' => "http://example.com/news.atom", 'folderId' => 3], 2112, 0, $this->feeds['db'][0], new ExceptionInput("idMissing"), new Response(['feeds' => [$this->feeds['rest'][0]]])], [['url' => "http://example.com/news.atom", 'folderId' => 3], 2112, 0, $this->feeds['db'][0], new ExceptionInput("idMissing"), HTTP::respJson(['feeds' => [$this->feeds['rest'][0]]])],
[['url' => "http://example.org/news.atom", 'folderId' => 8], 42, 4758915, $this->feeds['db'][1], true, new Response(['feeds' => [$this->feeds['rest'][1]], 'newestItemId' => 4758915])], [['url' => "http://example.org/news.atom", 'folderId' => 8], 42, 4758915, $this->feeds['db'][1], true, HTTP::respJson(['feeds' => [$this->feeds['rest'][1]], 'newestItemId' => 4758915])],
[['url' => "http://example.com/news.atom", 'folderId' => 3], new ExceptionInput("constraintViolation"), 0, $this->feeds['db'][0], new ExceptionInput("idMissing"), new EmptyResponse(409)], [['url' => "http://example.com/news.atom", 'folderId' => 3], new ExceptionInput("constraintViolation"), 0, $this->feeds['db'][0], new ExceptionInput("idMissing"), HTTP::respEmpty(409)],
[['url' => "http://example.org/news.atom", 'folderId' => 8], new ExceptionInput("constraintViolation"), 4758915, $this->feeds['db'][1], true, new EmptyResponse(409)], [['url' => "http://example.org/news.atom", 'folderId' => 8], new ExceptionInput("constraintViolation"), 4758915, $this->feeds['db'][1], true, HTTP::respEmpty(409)],
[[], $feedException, 0, [], false, new EmptyResponse(422)], [[], $feedException, 0, [], false, HTTP::respEmpty(422)],
[['url' => "http://example.net/news.atom", 'folderId' => -1], 47, 2112, $this->feeds['db'][2], new ExceptionInput("typeViolation"), new Response(['feeds' => [$this->feeds['rest'][2]], 'newestItemId' => 2112])], [['url' => "http://example.net/news.atom", 'folderId' => -1], 47, 2112, $this->feeds['db'][2], new ExceptionInput("typeViolation"), HTTP::respJson(['feeds' => [$this->feeds['rest'][2]], 'newestItemId' => 2112])],
]; ];
} }
public function testRemoveASubscription(): void { public function testRemoveASubscription(): void {
$this->dbMock->subscriptionRemove->with($this->userId, 1)->returns(true)->throws(new ExceptionInput("subjectMissing")); $this->dbMock->subscriptionRemove->with($this->userId, 1)->returns(true)->throws(new ExceptionInput("subjectMissing"));
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("DELETE", "/feeds/1")); $this->assertMessage($exp, $this->req("DELETE", "/feeds/1"));
// fail on the second invocation because it no longer exists // fail on the second invocation because it no longer exists
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("DELETE", "/feeds/1")); $this->assertMessage($exp, $this->req("DELETE", "/feeds/1"));
$this->dbMock->subscriptionRemove->times(2)->calledWith($this->userId, 1); $this->dbMock->subscriptionRemove->times(2)->calledWith($this->userId, 1);
} }
@ -571,17 +570,17 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, ['folder' => 2112])->throws(new ExceptionInput("idMissing")); // folder does not exist $this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, ['folder' => 2112])->throws(new ExceptionInput("idMissing")); // folder does not exist
$this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, ['folder' => -1])->throws(new ExceptionInput("typeViolation")); // folder is invalid $this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, ['folder' => -1])->throws(new ExceptionInput("typeViolation")); // folder is invalid
$this->dbMock->subscriptionPropertiesSet->with($this->userId, 42, $this->anything())->throws(new ExceptionInput("subjectMissing")); // subscription does not exist $this->dbMock->subscriptionPropertiesSet->with($this->userId, 42, $this->anything())->throws(new ExceptionInput("subjectMissing")); // subscription does not exist
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[0]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[0])));
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[1]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[1])));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[2]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[2])));
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("PUT", "/feeds/42/move", json_encode($in[3]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/42/move", json_encode($in[3])));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[4]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[4])));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[5]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/move", json_encode($in[5])));
} }
@ -601,17 +600,17 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, $this->identicalTo(['title' => ""]))->throws(new ExceptionInput("missing")); $this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, $this->identicalTo(['title' => ""]))->throws(new ExceptionInput("missing"));
$this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, $this->identicalTo(['title' => false]))->throws(new ExceptionInput("missing")); $this->dbMock->subscriptionPropertiesSet->with($this->userId, 1, $this->identicalTo(['title' => false]))->throws(new ExceptionInput("missing"));
$this->dbMock->subscriptionPropertiesSet->with($this->userId, 42, $this->anything())->throws(new ExceptionInput("subjectMissing")); $this->dbMock->subscriptionPropertiesSet->with($this->userId, 42, $this->anything())->throws(new ExceptionInput("subjectMissing"));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[0]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[0])));
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[1]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[1])));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[2]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[2])));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[3]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[3])));
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("PUT", "/feeds/42/rename", json_encode($in[4]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/42/rename", json_encode($in[4])));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[6]))); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/rename", json_encode($in[6])));
} }
@ -627,13 +626,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
], ],
]; ];
$this->dbMock->feedListStale->returns($this->v(array_column($out, "id"))); $this->dbMock->feedListStale->returns($this->v(array_column($out, "id")));
$exp = new Response(['feeds' => $out]); $exp = HTTP::respJson(['feeds' => $out]);
$this->assertMessage($exp, $this->req("GET", "/feeds/all")); $this->assertMessage($exp, $this->req("GET", "/feeds/all"));
} }
public function testListStaleFeedsWithoutAuthority(): void { public function testListStaleFeedsWithoutAuthority(): void {
$this->userMock->propertiesGet->returns(['admin' => false]); $this->userMock->propertiesGet->returns(['admin' => false]);
$exp = new EmptyResponse(403); $exp = HTTP::respEmpty(403);
$this->assertMessage($exp, $this->req("GET", "/feeds/all")); $this->assertMessage($exp, $this->req("GET", "/feeds/all"));
$this->dbMock->feedListStale->never()->called(); $this->dbMock->feedListStale->never()->called();
} }
@ -649,11 +648,11 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->feedUpdate->with(42)->returns(true); $this->dbMock->feedUpdate->with(42)->returns(true);
$this->dbMock->feedUpdate->with(2112)->throws(new ExceptionInput("subjectMissing")); $this->dbMock->feedUpdate->with(2112)->throws(new ExceptionInput("subjectMissing"));
$this->dbMock->feedUpdate->with($this->lessThan(1))->throws(new ExceptionInput("typeViolation")); $this->dbMock->feedUpdate->with($this->lessThan(1))->throws(new ExceptionInput("typeViolation"));
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[0]))); $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[0])));
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[1]))); $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[1])));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[2]))); $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[2])));
$this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[3]))); $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[3])));
$this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[4]))); $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[4])));
@ -661,7 +660,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
public function testUpdateAFeedWithoutAuthority(): void { public function testUpdateAFeedWithoutAuthority(): void {
$this->userMock->propertiesGet->returns(['admin' => false]); $this->userMock->propertiesGet->returns(['admin' => false]);
$exp = new EmptyResponse(403); $exp = HTTP::respEmpty(403);
$this->assertMessage($exp, $this->req("GET", "/feeds/update", ['feedId' => 42])); $this->assertMessage($exp, $this->req("GET", "/feeds/update", ['feedId' => 42]));
$this->dbMock->feedUpdate->never()->called(); $this->dbMock->feedUpdate->never()->called();
} }
@ -683,8 +682,8 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$c = (new Context)->hidden(false); $c = (new Context)->hidden(false);
$t = Date::normalize(time()); $t = Date::normalize(time());
$out = new Result($this->v($this->articles['db'])); $out = new Result($this->v($this->articles['db']));
$r200 = new Response(['items' => $this->articles['rest']]); $r200 = HTTP::respJson(['items' => $this->articles['rest']]);
$r422 = new EmptyResponse(422); $r422 = HTTP::respEmpty(422);
return [ return [
["/items", [], clone $c, $out, $r200], ["/items", [], clone $c, $out, $r200],
["/items", ['type' => 0, 'id' => 42], (clone $c)->subscription(42), new ExceptionInput("idMissing"), $r422], ["/items", ['type' => 0, 'id' => 42], (clone $c)->subscription(42), new ExceptionInput("idMissing"), $r422],
@ -720,13 +719,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$in = json_encode(['newestItemId' => 2112]); $in = json_encode(['newestItemId' => 2112]);
$this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->folder(1)->editionRange(null, 2112)->hidden(false)))->returns(42); $this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->folder(1)->editionRange(null, 2112)->hidden(false)))->returns(42);
$this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->folder(42)->editionRange(null, 2112)->hidden(false)))->throws(new ExceptionInput("idMissing")); // folder doesn't exist $this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->folder(42)->editionRange(null, 2112)->hidden(false)))->throws(new ExceptionInput("idMissing")); // folder doesn't exist
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("PUT", "/folders/1/read", $in)); $this->assertMessage($exp, $this->req("PUT", "/folders/1/read", $in));
$this->assertMessage($exp, $this->req("PUT", "/folders/1/read?newestItemId=2112")); $this->assertMessage($exp, $this->req("PUT", "/folders/1/read?newestItemId=2112"));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/folders/1/read")); $this->assertMessage($exp, $this->req("PUT", "/folders/1/read"));
$this->assertMessage($exp, $this->req("PUT", "/folders/1/read?newestItemId=ook")); $this->assertMessage($exp, $this->req("PUT", "/folders/1/read?newestItemId=ook"));
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("PUT", "/folders/42/read", $in)); $this->assertMessage($exp, $this->req("PUT", "/folders/42/read", $in));
} }
@ -735,13 +734,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$in = json_encode(['newestItemId' => 2112]); $in = json_encode(['newestItemId' => 2112]);
$this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->subscription(1)->editionRange(null, 2112)->hidden(false)))->returns(42); $this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->subscription(1)->editionRange(null, 2112)->hidden(false)))->returns(42);
$this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->subscription(42)->editionRange(null, 2112)->hidden(false)))->throws(new ExceptionInput("idMissing")); // subscription doesn't exist $this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->subscription(42)->editionRange(null, 2112)->hidden(false)))->throws(new ExceptionInput("idMissing")); // subscription doesn't exist
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/read", $in)); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/read", $in));
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/read?newestItemId=2112")); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/read?newestItemId=2112"));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/read")); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/read"));
$this->assertMessage($exp, $this->req("PUT", "/feeds/1/read?newestItemId=ook")); $this->assertMessage($exp, $this->req("PUT", "/feeds/1/read?newestItemId=ook"));
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("PUT", "/feeds/42/read", $in)); $this->assertMessage($exp, $this->req("PUT", "/feeds/42/read", $in));
} }
@ -749,10 +748,10 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$read = ['read' => true]; $read = ['read' => true];
$in = json_encode(['newestItemId' => 2112]); $in = json_encode(['newestItemId' => 2112]);
$this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->editionRange(null, 2112)))->returns(42); $this->dbMock->articleMark->with($this->userId, $read, $this->equalTo((new Context)->editionRange(null, 2112)))->returns(42);
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("PUT", "/items/read", $in)); $this->assertMessage($exp, $this->req("PUT", "/items/read", $in));
$this->assertMessage($exp, $this->req("PUT", "/items/read?newestItemId=2112")); $this->assertMessage($exp, $this->req("PUT", "/items/read?newestItemId=2112"));
$exp = new EmptyResponse(422); $exp = HTTP::respEmpty(422);
$this->assertMessage($exp, $this->req("PUT", "/items/read")); $this->assertMessage($exp, $this->req("PUT", "/items/read"));
$this->assertMessage($exp, $this->req("PUT", "/items/read?newestItemId=ook")); $this->assertMessage($exp, $this->req("PUT", "/items/read?newestItemId=ook"));
} }
@ -770,12 +769,12 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->articleMark->with($this->userId, $star, $this->equalTo((new Context)->article(2112)))->throws(new ExceptionInput("subjectMissing")); // article doesn't exist doesn't exist $this->dbMock->articleMark->with($this->userId, $star, $this->equalTo((new Context)->article(2112)))->throws(new ExceptionInput("subjectMissing")); // article doesn't exist doesn't exist
$this->dbMock->articleMark->with($this->userId, $unstar, $this->equalTo((new Context)->article(4)))->returns(42); $this->dbMock->articleMark->with($this->userId, $unstar, $this->equalTo((new Context)->article(4)))->returns(42);
$this->dbMock->articleMark->with($this->userId, $unstar, $this->equalTo((new Context)->article(1337)))->throws(new ExceptionInput("subjectMissing")); // article doesn't exist doesn't exist $this->dbMock->articleMark->with($this->userId, $unstar, $this->equalTo((new Context)->article(1337)))->throws(new ExceptionInput("subjectMissing")); // article doesn't exist doesn't exist
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("PUT", "/items/1/read")); $this->assertMessage($exp, $this->req("PUT", "/items/1/read"));
$this->assertMessage($exp, $this->req("PUT", "/items/2/unread")); $this->assertMessage($exp, $this->req("PUT", "/items/2/unread"));
$this->assertMessage($exp, $this->req("PUT", "/items/1/3/star")); $this->assertMessage($exp, $this->req("PUT", "/items/1/3/star"));
$this->assertMessage($exp, $this->req("PUT", "/items/4400/4/unstar")); $this->assertMessage($exp, $this->req("PUT", "/items/4400/4/unstar"));
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("PUT", "/items/42/read")); $this->assertMessage($exp, $this->req("PUT", "/items/42/read"));
$this->assertMessage($exp, $this->req("PUT", "/items/47/unread")); $this->assertMessage($exp, $this->req("PUT", "/items/47/unread"));
$this->assertMessage($exp, $this->req("PUT", "/items/1/2112/star")); $this->assertMessage($exp, $this->req("PUT", "/items/1/2112/star"));
@ -801,7 +800,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->articleMark->with($this->userId, $this->anything(), $this->anything())->returns(42); $this->dbMock->articleMark->with($this->userId, $this->anything(), $this->anything())->returns(42);
$this->dbMock->articleMark->with($this->userId, $this->anything(), $this->equalTo((new Context)->editions([])))->throws(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples $this->dbMock->articleMark->with($this->userId, $this->anything(), $this->equalTo((new Context)->editions([])))->throws(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples
$this->dbMock->articleMark->with($this->userId, $this->anything(), $this->equalTo((new Context)->articles([])))->throws(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples $this->dbMock->articleMark->with($this->userId, $this->anything(), $this->equalTo((new Context)->articles([])))->throws(new ExceptionInput("tooShort")); // data model function requires one valid integer for multiples
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("PUT", "/items/read/multiple")); $this->assertMessage($exp, $this->req("PUT", "/items/read/multiple"));
$this->assertMessage($exp, $this->req("PUT", "/items/unread/multiple")); $this->assertMessage($exp, $this->req("PUT", "/items/unread/multiple"));
$this->assertMessage($exp, $this->req("PUT", "/items/star/multiple")); $this->assertMessage($exp, $this->req("PUT", "/items/star/multiple"));
@ -854,44 +853,44 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
]; ];
$arr2['warnings']['improperlyConfiguredCron'] = true; $arr2['warnings']['improperlyConfiguredCron'] = true;
$arr2['warnings']['incorrectDbCharset'] = true; $arr2['warnings']['incorrectDbCharset'] = true;
$exp = new Response($arr1); $exp = HTTP::respJson($arr1);
$this->assertMessage($exp, $this->req("GET", "/status")); $this->assertMessage($exp, $this->req("GET", "/status"));
} }
public function testCleanUpBeforeUpdate(): void { public function testCleanUpBeforeUpdate(): void {
$this->dbMock->feedCleanup->with()->returns(true); $this->dbMock->feedCleanup->with()->returns(true);
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("GET", "/cleanup/before-update")); $this->assertMessage($exp, $this->req("GET", "/cleanup/before-update"));
$this->dbMock->feedCleanup->calledWith(); $this->dbMock->feedCleanup->calledWith();
} }
public function testCleanUpBeforeUpdateWithoutAuthority(): void { public function testCleanUpBeforeUpdateWithoutAuthority(): void {
$this->userMock->propertiesGet->returns(['admin' => false]); $this->userMock->propertiesGet->returns(['admin' => false]);
$exp = new EmptyResponse(403); $exp = HTTP::respEmpty(403);
$this->assertMessage($exp, $this->req("GET", "/cleanup/before-update")); $this->assertMessage($exp, $this->req("GET", "/cleanup/before-update"));
$this->dbMock->feedCleanup->never()->called(); $this->dbMock->feedCleanup->never()->called();
} }
public function testCleanUpAfterUpdate(): void { public function testCleanUpAfterUpdate(): void {
$this->dbMock->articleCleanup->with()->returns(true); $this->dbMock->articleCleanup->with()->returns(true);
$exp = new EmptyResponse(204); $exp = HTTP::respEmpty(204);
$this->assertMessage($exp, $this->req("GET", "/cleanup/after-update")); $this->assertMessage($exp, $this->req("GET", "/cleanup/after-update"));
$this->dbMock->articleCleanup->calledWith(); $this->dbMock->articleCleanup->calledWith();
} }
public function testCleanUpAfterUpdateWithoutAuthority(): void { public function testCleanUpAfterUpdateWithoutAuthority(): void {
$this->userMock->propertiesGet->returns(['admin' => false]); $this->userMock->propertiesGet->returns(['admin' => false]);
$exp = new EmptyResponse(403); $exp = HTTP::respEmpty(403);
$this->assertMessage($exp, $this->req("GET", "/cleanup/after-update")); $this->assertMessage($exp, $this->req("GET", "/cleanup/after-update"));
$this->dbMock->feedCleanup->never()->called(); $this->dbMock->feedCleanup->never()->called();
} }
public function testQueryTheUserStatus(): void { public function testQueryTheUserStatus(): void {
$act = $this->req("GET", "/user"); $act = $this->req("GET", "/user");
$exp = new Response([ $exp = HTTP::respJson([
'userId' => $this->userId, 'userId' => $this->userId,
'displayName' => $this->userId, 'displayName' => $this->userId,
'lastLoginTimestamp' => $this->approximateTime($act->getPayload()['lastLoginTimestamp'], new \DateTimeImmutable), 'lastLoginTimestamp' => $this->approximateTime(json_decode((string) $act->getBody(), true)['lastLoginTimestamp'], new \DateTimeImmutable),
'avatar' => null, 'avatar' => null,
]); ]);
$this->assertMessage($exp, $act); $this->assertMessage($exp, $act);
@ -906,8 +905,8 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->folderAdd->with($this->anything(), $in)->returns(1); $this->dbMock->folderAdd->with($this->anything(), $in)->returns(1);
$this->dbMock->folderPropertiesGet->with($this->userId, 1)->returns($this->v($out1)); $this->dbMock->folderPropertiesGet->with($this->userId, 1)->returns($this->v($out1));
$this->dbMock->folderPropertiesGet->with($this->userId, 2)->returns($this->v($out2)); $this->dbMock->folderPropertiesGet->with($this->userId, 2)->returns($this->v($out2));
$exp = new Response(['folders' => [$out1]]); $exp = HTTP::respJson(['folders' => [$out1]]);
$this->assertMessage($exp, $this->req("POST", "/folders?name=Hardware", json_encode($in))); $this->assertMessage($exp, $this->req("POST", $url, json_encode($in)));
} }
public function testMeldJsonAndQueryParameters(): void { public function testMeldJsonAndQueryParameters(): void {

11
tests/cases/REST/NextcloudNews/TestVersions.php

@ -6,10 +6,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\Arsse\TestCase\REST\NextcloudNews; namespace JKingWeb\Arsse\TestCase\REST\NextcloudNews;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\REST\NextcloudNews\Versions; use JKingWeb\Arsse\REST\NextcloudNews\Versions;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse;
/** @covers \JKingWeb\Arsse\REST\NextcloudNews\Versions */ /** @covers \JKingWeb\Arsse\REST\NextcloudNews\Versions */
class TestVersions extends \JKingWeb\Arsse\Test\AbstractTest { class TestVersions extends \JKingWeb\Arsse\Test\AbstractTest {
@ -25,24 +24,24 @@ class TestVersions extends \JKingWeb\Arsse\Test\AbstractTest {
} }
public function testFetchVersionList(): void { public function testFetchVersionList(): void {
$exp = new Response(['apiLevels' => ['v1-2']]); $exp = HTTP::respJson(['apiLevels' => ['v1-2']]);
$this->assertMessage($exp, $this->req("GET", "/")); $this->assertMessage($exp, $this->req("GET", "/"));
$this->assertMessage($exp, $this->req("GET", "/")); $this->assertMessage($exp, $this->req("GET", "/"));
$this->assertMessage($exp, $this->req("GET", "/")); $this->assertMessage($exp, $this->req("GET", "/"));
} }
public function testRespondToOptionsRequest(): void { public function testRespondToOptionsRequest(): void {
$exp = new EmptyResponse(204, ['Allow' => "HEAD,GET"]); $exp = HTTP::respEmpty(204, ['Allow' => "HEAD,GET"]);
$this->assertMessage($exp, $this->req("OPTIONS", "/")); $this->assertMessage($exp, $this->req("OPTIONS", "/"));
} }
public function testUseIncorrectMethod(): void { public function testUseIncorrectMethod(): void {
$exp = new EmptyResponse(405, ['Allow' => "HEAD,GET"]); $exp = HTTP::respEmpty(405, ['Allow' => "HEAD,GET"]);
$this->assertMessage($exp, $this->req("POST", "/")); $this->assertMessage($exp, $this->req("POST", "/"));
} }
public function testUseIncorrectPath(): void { public function testUseIncorrectPath(): void {
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("GET", "/ook")); $this->assertMessage($exp, $this->req("GET", "/ook"));
$this->assertMessage($exp, $this->req("OPTIONS", "/ook")); $this->assertMessage($exp, $this->req("OPTIONS", "/ook"));
} }

69
tests/cases/REST/TestREST.php

@ -12,13 +12,12 @@ use JKingWeb\Arsse\REST;
use JKingWeb\Arsse\REST\Exception501; use JKingWeb\Arsse\REST\Exception501;
use JKingWeb\Arsse\REST\NextcloudNews\V1_2 as NCN; use JKingWeb\Arsse\REST\NextcloudNews\V1_2 as NCN;
use JKingWeb\Arsse\REST\TinyTinyRSS\API as TTRSS; use JKingWeb\Arsse\REST\TinyTinyRSS\API as TTRSS;
use JKingWeb\Arsse\Misc\HTTP;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Request; use GuzzleHttp\Psr7\Response;
use Laminas\Diactoros\Response; use GuzzleHttp\Psr7\Request;
use Laminas\Diactoros\ServerRequest; use GuzzleHttp\Psr7\ServerRequest;
use Laminas\Diactoros\Response\TextResponse;
use Laminas\Diactoros\Response\EmptyResponse;
/** @covers \JKingWeb\Arsse\REST */ /** @covers \JKingWeb\Arsse\REST */
class TestREST extends \JKingWeb\Arsse\Test\AbstractTest { class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
@ -69,7 +68,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
$this->userMock->auth->with("someone.else@example.com", "")->returns(true); $this->userMock->auth->with("someone.else@example.com", "")->returns(true);
Arsse::$user = $this->userMock->get(); Arsse::$user = $this->userMock->get();
// create an input server request // create an input server request
$req = new ServerRequest($serverParams); $req = new ServerRequest("GET", "/", [], null, "1.1", $serverParams);
// create the expected output // create the expected output
$exp = $req; $exp = $req;
foreach ($expAttr as $key => $value) { foreach ($expAttr as $key => $value) {
@ -95,7 +94,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
public function testSendAuthenticationChallenges(): void { public function testSendAuthenticationChallenges(): void {
self::setConf(); self::setConf();
$r = new REST; $r = new REST;
$in = new EmptyResponse(401); $in = HTTP::respEmpty(401);
$exp = $in->withHeader("WWW-Authenticate", 'Basic realm="OOK", charset="UTF-8"'); $exp = $in->withHeader("WWW-Authenticate", 'Basic realm="OOK", charset="UTF-8"');
$act = $r->challenge($in, "OOK"); $act = $r->challenge($in, "OOK");
$this->assertMessage($exp, $act); $this->assertMessage($exp, $act);
@ -155,7 +154,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
return $origin; return $origin;
}); });
$headers = isset($origin) ? ['Origin' => $origin] : []; $headers = isset($origin) ? ['Origin' => $origin] : [];
$req = new Request("", "GET", "php://memory", $headers); $req = new Request("GET", "", $headers);
$act = $rMock->get()->corsNegotiate($req, $allowed, $denied); $act = $rMock->get()->corsNegotiate($req, $allowed, $denied);
$this->assertSame($exp, $act); $this->assertSame($exp, $act);
} }
@ -188,9 +187,9 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
/** @dataProvider provideCorsHeaders */ /** @dataProvider provideCorsHeaders */
public function testAddCorsHeaders(string $reqMethod, array $reqHeaders, array $resHeaders, array $expHeaders): void { 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); $req = new Request($reqMethod, "php://memory", $reqHeaders);
$res = new EmptyResponse(204, $resHeaders); $res = HTTP::respEmpty(204, $resHeaders);
$exp = new EmptyResponse(204, $expHeaders); $exp = HTTP::respEmpty(204, $expHeaders);
$act = $r->corsApply($res, $req); $act = $r->corsApply($res, $req);
$this->assertMessage($exp, $act); $this->assertMessage($exp, $act);
} }
@ -242,7 +241,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
["OPTIONS", ['Origin' => "http://example", 'Access-Control-Request-Headers' => ["Content-Type", "If-None-Match"]], [], [ ["OPTIONS", ['Origin' => "http://example", 'Access-Control-Request-Headers' => ["Content-Type", "If-None-Match"]], [], [
'Access-Control-Allow-Origin' => "http://example", 'Access-Control-Allow-Origin' => "http://example",
'Access-Control-Allow-Credentials' => "true", 'Access-Control-Allow-Credentials' => "true",
'Access-Control-Allow-Headers' => "Content-Type,If-None-Match", 'Access-Control-Allow-Headers' => "Content-Type, If-None-Match",
'Access-Control-Max-Age' => (string) (60 * 60 * 24), 'Access-Control-Max-Age' => (string) (60 * 60 * 24),
'Vary' => "Origin", 'Vary' => "Origin",
]], ]],
@ -267,21 +266,21 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
$stream = fopen("php://memory", "w+b"); $stream = fopen("php://memory", "w+b");
fwrite($stream, "ook"); fwrite($stream, "ook");
return [ return [
[new EmptyResponse(204), new EmptyResponse(204)], [HTTP::respEmpty(204), HTTP::respEmpty(204)],
[new EmptyResponse(401), new EmptyResponse(401, ['WWW-Authenticate' => "Fake Value"])], [HTTP::respEmpty(401), HTTP::respEmpty(401, ['WWW-Authenticate' => "Fake Value"])],
[new EmptyResponse(204, ['Allow' => "PUT"]), new EmptyResponse(204, ['Allow' => "PUT, OPTIONS"])], [HTTP::respEmpty(204, ['Allow' => "PUT"]), HTTP::respEmpty(204, ['Allow' => "PUT, OPTIONS"])],
[new EmptyResponse(204, ['Allow' => "PUT, OPTIONS"]), new EmptyResponse(204, ['Allow' => "PUT, OPTIONS"])], [HTTP::respEmpty(204, ['Allow' => "PUT, OPTIONS"]), HTTP::respEmpty(204, ['Allow' => "PUT, OPTIONS"])],
[new EmptyResponse(204, ['Allow' => "PUT,OPTIONS"]), new EmptyResponse(204, ['Allow' => "PUT, OPTIONS"])], [HTTP::respEmpty(204, ['Allow' => "PUT,OPTIONS"]), HTTP::respEmpty(204, ['Allow' => "PUT, OPTIONS"])],
[new EmptyResponse(204, ['Allow' => ["PUT", "OPTIONS"]]), new EmptyResponse(204, ['Allow' => "PUT, OPTIONS"])], [HTTP::respEmpty(204, ['Allow' => ["PUT", "OPTIONS"]]), HTTP::respEmpty(204, ['Allow' => "PUT, OPTIONS"])],
[new EmptyResponse(204, ['Allow' => ["PUT, DELETE", "OPTIONS"]]), new EmptyResponse(204, ['Allow' => "PUT, DELETE, OPTIONS"])], [HTTP::respEmpty(204, ['Allow' => ["PUT, DELETE", "OPTIONS"]]), HTTP::respEmpty(204, ['Allow' => "PUT, DELETE, OPTIONS"])],
[new EmptyResponse(204, ['Allow' => "HEAD,GET"]), new EmptyResponse(204, ['Allow' => "HEAD, GET, OPTIONS"])], [HTTP::respEmpty(204, ['Allow' => "HEAD,GET"]), HTTP::respEmpty(204, ['Allow' => "HEAD, GET, OPTIONS"])],
[new EmptyResponse(204, ['Allow' => "GET"]), new EmptyResponse(204, ['Allow' => "GET, HEAD, OPTIONS"])], [HTTP::respEmpty(204, ['Allow' => "GET"]), HTTP::respEmpty(204, ['Allow' => "GET, HEAD, OPTIONS"])],
[new TextResponse("ook", 200), new TextResponse("ook", 200, ['Content-Length' => "3"])], [HTTP::respText("ook", 200), HTTP::respText("ook", 200, ['Content-Length' => "3"])],
[new TextResponse("", 200), new TextResponse("", 200, ['Content-Length' => "0"])], [HTTP::respText("", 200), HTTP::respText("", 200, ['Content-Length' => "0"])],
[new TextResponse("ook", 404), new TextResponse("ook", 404, ['Content-Length' => "3"])], [HTTP::respText("ook", 404), HTTP::respText("ook", 404, ['Content-Length' => "3"])],
[new TextResponse("", 404), new TextResponse("", 404)], [HTTP::respText("", 404), HTTP::respText("", 404)],
[new Response($stream, 200), new Response($stream, 200, ['Content-Length' => "3"]), new Request("", "GET")], [new Response(200, [], $stream), new Response(200, ['Content-Length' => "3"], $stream), new Request("GET", "")],
[new Response($stream, 200), new EmptyResponse(200, ['Content-Length' => "3"]), new Request("", "HEAD")], [new Response(200, [], $stream), HTTP::respEmpty(200, ['Content-Length' => "3"]), new Request("HEAD", "")],
]; ];
} }
@ -296,7 +295,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
}); });
if ($called) { if ($called) {
$hMock = $this->mock($class); $hMock = $this->mock($class);
$hMock->dispatch->returns(new EmptyResponse(204)); $hMock->dispatch->returns(HTTP::respEmpty(204));
$this->objMock->get->with($class)->returns($hMock); $this->objMock->get->with($class)->returns($hMock);
Arsse::$obj = $this->objMock->get(); Arsse::$obj = $this->objMock->get();
} }
@ -317,13 +316,13 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest {
public function provideMockRequests(): iterable { public function provideMockRequests(): iterable {
return [ return [
[new ServerRequest([], [], "/index.php/apps/news/api/v1-2/feeds", "GET"), "GET", true, NCN::class, "/feeds"], [new ServerRequest("GET", "/index.php/apps/news/api/v1-2/feeds"), "GET", true, NCN::class, "/feeds"],
[new ServerRequest([], [], "/index.php/apps/news/api/v1-2/feeds", "HEAD"), "GET", true, NCN::class, "/feeds"], [new ServerRequest("GET", "/index.php/apps/news/api/v1-2/feeds"), "GET", true, NCN::class, "/feeds"],
[new ServerRequest([], [], "/index.php/apps/news/api/v1-2/feeds", "get"), "GET", true, NCN::class, "/feeds"], [new ServerRequest("get", "/index.php/apps/news/api/v1-2/feeds"), "GET", true, NCN::class, "/feeds"],
[new ServerRequest([], [], "/index.php/apps/news/api/v1-2/feeds", "head"), "GET", true, NCN::class, "/feeds"], [new ServerRequest("head", "/index.php/apps/news/api/v1-2/feeds"), "GET", true, NCN::class, "/feeds"],
[new ServerRequest([], [], "/tt-rss/api/", "POST"), "POST", true, TTRSS::class, "/"], [new ServerRequest("POST", "/tt-rss/api/"), "POST", true, TTRSS::class, "/"],
[new ServerRequest([], [], "/no/such/api/", "HEAD"), "GET", false], [new ServerRequest("HEAD", "/no/such/api/"), "GET", false],
[new ServerRequest([], [], "/no/such/api/", "GET"), "GET", false], [new ServerRequest("GET", "/no/such/api/"), "GET", false],
]; ];
} }
} }

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

@ -11,14 +11,13 @@ use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\Test\Result; use JKingWeb\Arsse\Test\Result;
use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Context\Context;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\Transaction; use JKingWeb\Arsse\Db\Transaction;
use JKingWeb\Arsse\REST\TinyTinyRSS\API; use JKingWeb\Arsse\REST\TinyTinyRSS\API;
use JKingWeb\Arsse\Feed\Exception as FeedException; use JKingWeb\Arsse\Feed\Exception as FeedException;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\EmptyResponse;
/** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\API<extended> /** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\API<extended>
* @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Exception */ * @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Exception */
@ -166,17 +165,17 @@ LONG_STRING;
return $this->req($data, "POST", "", null, $user); return $this->req($data, "POST", "", null, $user);
} }
protected function respGood($content = null, $seq = 0): Response { protected function respGood($content = null, $seq = 0): ResponseInterface {
return new Response([ return HTTP::respJson([
'seq' => $seq, 'seq' => $seq,
'status' => 0, 'status' => 0,
'content' => $content, 'content' => $content,
]); ]);
} }
protected function respErr(string $msg, $content = [], $seq = 0): Response { protected function respErr(string $msg, $content = [], $seq = 0): ResponseInterface {
$err = ['error' => $msg]; $err = ['error' => $msg];
return new Response([ return HTTP::respJson([
'seq' => $seq, 'seq' => $seq,
'status' => 1, 'status' => 1,
'content' => array_merge($err, $content, $err), 'content' => array_merge($err, $content, $err),
@ -188,12 +187,12 @@ LONG_STRING;
$this->assertMessage($exp, $this->req(null, "POST", "", "")); $this->assertMessage($exp, $this->req(null, "POST", "", ""));
$this->assertMessage($exp, $this->req(null, "POST", "/", "")); $this->assertMessage($exp, $this->req(null, "POST", "/", ""));
$this->assertMessage($exp, $this->req(null, "POST", "/index.php", "")); $this->assertMessage($exp, $this->req(null, "POST", "/index.php", ""));
$exp = new EmptyResponse(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req(null, "POST", "/bad/path", "")); $this->assertMessage($exp, $this->req(null, "POST", "/bad/path", ""));
} }
public function testHandleOptionsRequest(): void { public function testHandleOptionsRequest(): void {
$exp = new EmptyResponse(204, [ $exp = HTTP::respEmpty(204, [
'Allow' => "POST", 'Allow' => "POST",
'Accept' => "application/json, text/json", 'Accept' => "application/json, text/json",
]); ]);
@ -215,7 +214,7 @@ LONG_STRING;
$this->userMock->auth->with("jane.doe@example.com", "superman")->returns(true); $this->userMock->auth->with("jane.doe@example.com", "superman")->returns(true);
$this->dbMock->sessionCreate->with("john.doe@example.com")->returns("PriestsOfSyrinx", "SolarFederation"); $this->dbMock->sessionCreate->with("john.doe@example.com")->returns("PriestsOfSyrinx", "SolarFederation");
$this->dbMock->sessionCreate->with("jane.doe@example.com")->returns("ClockworkAngels", "SevenCitiesOfGold"); $this->dbMock->sessionCreate->with("jane.doe@example.com")->returns("ClockworkAngels", "SevenCitiesOfGold");
if ($sessions instanceof EmptyResponse) { if ($sessions instanceof ResponseInterface) {
$exp1 = $sessions; $exp1 = $sessions;
$exp2 = $sessions; $exp2 = $sessions;
} elseif ($sessions) { } elseif ($sessions) {
@ -260,7 +259,7 @@ LONG_STRING;
'op' => "isLoggedIn", 'op' => "isLoggedIn",
'sid' => $data, 'sid' => $data,
]; ];
if ($result instanceof EmptyResponse) { if ($result instanceof ResponseInterface) {
$exp1 = $result; $exp1 = $result;
$exp2 = null; $exp2 = null;
} elseif ($result) { } elseif ($result) {
@ -333,7 +332,7 @@ LONG_STRING;
'userHTTPAuthRequired' => true, 'userHTTPAuthRequired' => true,
'userSessionEnforced' => false, 'userSessionEnforced' => false,
]; ];
$http401 = new EmptyResponse(401); $http401 = HTTP::respEmpty(401);
if ($type === "login") { if ($type === "login") {
return [ return [
// conf, user, data, result // conf, user, data, result
@ -532,7 +531,7 @@ LONG_STRING;
'user' => $this->userId, 'user' => $this->userId,
'password' => "secret", 'password' => "secret",
]; ];
$exp = new EmptyResponse(500); $exp = HTTP::respEmpty(500);
$this->assertMessage($exp, $this->req($data)); $this->assertMessage($exp, $this->req($data));
} }
@ -1686,10 +1685,10 @@ LONG_STRING;
$this->assertMessage($this->outputHeadlines(1), $test); $this->assertMessage($this->outputHeadlines(1), $test);
// test 'show_content' // test 'show_content'
$test = $this->req($in[1]); $test = $this->req($in[1]);
$this->assertArrayHasKey("content", $test->getPayload()['content'][0]); $this->assertArrayHasKey("content", $this->extractMessageJson($test)['content'][0]);
$this->assertArrayHasKey("content", $test->getPayload()['content'][1]); $this->assertArrayHasKey("content", $this->extractMessageJson($test)['content'][1]);
foreach ($this->generateHeadlines(1) as $key => $row) { foreach ($this->generateHeadlines(1) as $key => $row) {
$this->assertSame($row['content'], $test->getPayload()['content'][$key]['content']); $this->assertSame($row['content'], $this->extractMessageJson($test)['content'][$key]['content']);
} }
// test 'include_attachments' // test 'include_attachments'
$test = $this->req($in[2]); $test = $this->req($in[2]);
@ -1705,22 +1704,22 @@ LONG_STRING;
'post_id' => "2112", 'post_id' => "2112",
], ],
]; ];
$this->assertArrayHasKey("attachments", $test->getPayload()['content'][0]); $this->assertArrayHasKey("attachments", $this->extractMessageJson($test)['content'][0]);
$this->assertArrayHasKey("attachments", $test->getPayload()['content'][1]); $this->assertArrayHasKey("attachments", $this->extractMessageJson($test)['content'][1]);
$this->assertSame([], $test->getPayload()['content'][0]['attachments']); $this->assertSame([], $this->extractMessageJson($test)['content'][0]['attachments']);
$this->assertSame($exp, $test->getPayload()['content'][1]['attachments']); $this->assertSame($exp, $this->extractMessageJson($test)['content'][1]['attachments']);
// test 'include_header' // test 'include_header'
$test = $this->req($in[3]); $test = $this->req($in[3]);
$exp = $this->respGood([ $exp = $this->respGood([
['id' => -4, 'is_cat' => false, 'first_id' => 1], ['id' => -4, 'is_cat' => false, 'first_id' => 1],
$this->outputHeadlines(1)->getPayload()['content'], $this->extractMessageJson($this->outputHeadlines(1))['content'],
]); ]);
$this->assertMessage($exp, $test); $this->assertMessage($exp, $test);
// test 'include_header' with a category // test 'include_header' with a category
$test = $this->req($in[4]); $test = $this->req($in[4]);
$exp = $this->respGood([ $exp = $this->respGood([
['id' => -3, 'is_cat' => true, 'first_id' => 1], ['id' => -3, 'is_cat' => true, 'first_id' => 1],
$this->outputHeadlines(1)->getPayload()['content'], $this->extractMessageJson($this->outputHeadlines(1))['content'],
]); ]);
$this->assertMessage($exp, $test); $this->assertMessage($exp, $test);
// test 'include_header' with an empty result // test 'include_header' with an empty result
@ -1742,7 +1741,7 @@ LONG_STRING;
$test = $this->req($in[7]); $test = $this->req($in[7]);
$exp = $this->respGood([ $exp = $this->respGood([
['id' => -4, 'is_cat' => false, 'first_id' => 0], ['id' => -4, 'is_cat' => false, 'first_id' => 0],
$this->outputHeadlines(1)->getPayload()['content'], $this->extractMessageJson($this->outputHeadlines(1))['content'],
]); ]);
$this->assertMessage($exp, $test); $this->assertMessage($exp, $test);
// test 'include_header' with skip // test 'include_header' with skip
@ -1750,24 +1749,24 @@ LONG_STRING;
$test = $this->req($in[8]); $test = $this->req($in[8]);
$exp = $this->respGood([ $exp = $this->respGood([
['id' => 42, 'is_cat' => false, 'first_id' => 1867], ['id' => 42, 'is_cat' => false, 'first_id' => 1867],
$this->outputHeadlines(1)->getPayload()['content'], $this->extractMessageJson($this->outputHeadlines(1))['content'],
]); ]);
$this->assertMessage($exp, $test); $this->assertMessage($exp, $test);
// test 'include_header' with skip and ascending order // test 'include_header' with skip and ascending order
$test = $this->req($in[9]); $test = $this->req($in[9]);
$exp = $this->respGood([ $exp = $this->respGood([
['id' => 42, 'is_cat' => false, 'first_id' => 0], ['id' => 42, 'is_cat' => false, 'first_id' => 0],
$this->outputHeadlines(1)->getPayload()['content'], $this->extractMessageJson($this->outputHeadlines(1))['content'],
]); ]);
$this->assertMessage($exp, $test); $this->assertMessage($exp, $test);
// test 'show_excerpt' // test 'show_excerpt'
$exp1 = "“This & that, you know‽”"; $exp1 = "“This & that, you know‽”";
$exp2 = "Pour vous faire mieux connaitre d’ou\u{300} vient l’erreur de ceux qui bla\u{302}ment la volupte\u{301}, et qui louent en…"; $exp2 = "Pour vous faire mieux connaitre d’ou\u{300} vient l’erreur de ceux qui bla\u{302}ment la volupte\u{301}, et qui louent en…";
$test = $this->req($in[10]); $test = $this->req($in[10]);
$this->assertArrayHasKey("excerpt", $test->getPayload()['content'][0]); $this->assertArrayHasKey("excerpt", $this->extractMessageJson($test)['content'][0]);
$this->assertArrayHasKey("excerpt", $test->getPayload()['content'][1]); $this->assertArrayHasKey("excerpt", $this->extractMessageJson($test)['content'][1]);
$this->assertSame($exp1, $test->getPayload()['content'][0]['excerpt']); $this->assertSame($exp1, $this->extractMessageJson($test)['content'][0]['excerpt']);
$this->assertSame($exp2, $test->getPayload()['content'][1]['excerpt']); $this->assertSame($exp2, $this->extractMessageJson($test)['content'][1]['excerpt']);
} }
protected function generateHeadlines(int $id): Result { protected function generateHeadlines(int $id): Result {
@ -1815,7 +1814,7 @@ LONG_STRING;
])); ]));
} }
protected function outputHeadlines(int $id): Response { protected function outputHeadlines(int $id): ResponseInterface {
return $this->respGood([ return $this->respGood([
[ [
'id' => $id, 'id' => $id,

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

@ -9,10 +9,10 @@ namespace JKingWeb\Arsse\TestCase\REST\TinyTinyRSS;
use JKingWeb\Arsse\Arsse; use JKingWeb\Arsse\Arsse;
use JKingWeb\Arsse\User; use JKingWeb\Arsse\User;
use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Database;
use JKingWeb\Arsse\Misc\HTTP;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\REST\TinyTinyRSS\Icon; use JKingWeb\Arsse\REST\TinyTinyRSS\Icon;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\EmptyResponse as Response;
/** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Icon<extended> */ /** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Icon<extended> */
class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest { class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest {
@ -51,21 +51,21 @@ class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->subscriptionIcon->with($this->anything(), 2112, false)->returns(['url' => "http://example.net/logo.png"]); $this->dbMock->subscriptionIcon->with($this->anything(), 2112, false)->returns(['url' => "http://example.net/logo.png"]);
$this->dbMock->subscriptionIcon->with($this->anything(), 1337, false)->returns(['url' => "http://example.org/icon.gif\r\nLocation: http://bad.example.com/"]); $this->dbMock->subscriptionIcon->with($this->anything(), 1337, false)->returns(['url' => "http://example.org/icon.gif\r\nLocation: http://bad.example.com/"]);
// these requests should succeed // these requests should succeed
$exp = new Response(301, ['Location' => "http://example.com/favicon.ico"]); $exp = HTTP::respEmpty(301, ['Location' => "http://example.com/favicon.ico"]);
$this->assertMessage($exp, $this->req("42.ico")); $this->assertMessage($exp, $this->req("42.ico"));
$exp = new Response(301, ['Location' => "http://example.net/logo.png"]); $exp = HTTP::respEmpty(301, ['Location' => "http://example.net/logo.png"]);
$this->assertMessage($exp, $this->req("2112.ico")); $this->assertMessage($exp, $this->req("2112.ico"));
$exp = new Response(301, ['Location' => "http://example.org/icon.gif"]); $exp = HTTP::respEmpty(301, ['Location' => "http://example.org/icon.gif"]);
$this->assertMessage($exp, $this->req("1337.ico")); $this->assertMessage($exp, $this->req("1337.ico"));
// these requests should fail // these requests should fail
$exp = new Response(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->req("ook.ico")); $this->assertMessage($exp, $this->req("ook.ico"));
$this->assertMessage($exp, $this->req("ook")); $this->assertMessage($exp, $this->req("ook"));
$this->assertMessage($exp, $this->req("47.ico")); $this->assertMessage($exp, $this->req("47.ico"));
$this->assertMessage($exp, $this->req("2112.png")); $this->assertMessage($exp, $this->req("2112.png"));
$this->assertMessage($exp, $this->req("1123.ico")); $this->assertMessage($exp, $this->req("1123.ico"));
// only GET is allowed // only GET is allowed
$exp = new Response(405, ['Allow' => "GET"]); $exp = HTTP::respEmpty(405, ['Allow' => "GET"]);
$this->assertMessage($exp, $this->req("2112.ico", "PUT")); $this->assertMessage($exp, $this->req("2112.ico", "PUT"));
} }
@ -79,32 +79,32 @@ class TestIcon extends \JKingWeb\Arsse\Test\AbstractTest {
$this->dbMock->subscriptionIcon->with(null, 2112, false)->returns($url); $this->dbMock->subscriptionIcon->with(null, 2112, false)->returns($url);
$this->dbMock->subscriptionIcon->with(null, 1337, false)->returns($url); $this->dbMock->subscriptionIcon->with(null, 1337, false)->returns($url);
// these requests should succeed // these requests should succeed
$exp = new Response(301, ['Location' => "http://example.org/icon.gif"]); $exp = HTTP::respEmpty(301, ['Location' => "http://example.org/icon.gif"]);
$this->assertMessage($exp, $this->req("42.ico")); $this->assertMessage($exp, $this->req("42.ico"));
$this->assertMessage($exp, $this->req("2112.ico")); $this->assertMessage($exp, $this->req("2112.ico"));
$this->assertMessage($exp, $this->req("1337.ico")); $this->assertMessage($exp, $this->req("1337.ico"));
$this->assertMessage($exp, $this->reqAuth("42.ico")); $this->assertMessage($exp, $this->reqAuth("42.ico"));
$this->assertMessage($exp, $this->reqAuth("1337.ico")); $this->assertMessage($exp, $this->reqAuth("1337.ico"));
// these requests should fail // these requests should fail
$exp = new Response(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->reqAuth("2112.ico")); $this->assertMessage($exp, $this->reqAuth("2112.ico"));
$exp = new Response(401); $exp = HTTP::respEmpty(401);
$this->assertMessage($exp, $this->reqAuthFailed("42.ico")); $this->assertMessage($exp, $this->reqAuthFailed("42.ico"));
$this->assertMessage($exp, $this->reqAuthFailed("1337.ico")); $this->assertMessage($exp, $this->reqAuthFailed("1337.ico"));
// with HTTP auth required, only authenticated requests should succeed // with HTTP auth required, only authenticated requests should succeed
self::setConf(['userHTTPAuthRequired' => true]); self::setConf(['userHTTPAuthRequired' => true]);
$exp = new Response(301, ['Location' => "http://example.org/icon.gif"]); $exp = HTTP::respEmpty(301, ['Location' => "http://example.org/icon.gif"]);
$this->assertMessage($exp, $this->reqAuth("42.ico")); $this->assertMessage($exp, $this->reqAuth("42.ico"));
$this->assertMessage($exp, $this->reqAuth("1337.ico")); $this->assertMessage($exp, $this->reqAuth("1337.ico"));
// anything else should fail // anything else should fail
$exp = new Response(401); $exp = HTTP::respEmpty(401);
$this->assertMessage($exp, $this->req("42.ico")); $this->assertMessage($exp, $this->req("42.ico"));
$this->assertMessage($exp, $this->req("2112.ico")); $this->assertMessage($exp, $this->req("2112.ico"));
$this->assertMessage($exp, $this->req("1337.ico")); $this->assertMessage($exp, $this->req("1337.ico"));
$this->assertMessage($exp, $this->reqAuthFailed("42.ico")); $this->assertMessage($exp, $this->reqAuthFailed("42.ico"));
$this->assertMessage($exp, $this->reqAuthFailed("1337.ico")); $this->assertMessage($exp, $this->reqAuthFailed("1337.ico"));
// resources for the wrtong user should still fail, too // resources for the wrtong user should still fail, too
$exp = new Response(404); $exp = HTTP::respEmpty(404);
$this->assertMessage($exp, $this->reqAuth("2112.ico")); $this->assertMessage($exp, $this->reqAuth("2112.ico"));
} }
} }

36
tests/lib/AbstractTest.php

@ -17,15 +17,13 @@ use JKingWeb\Arsse\Db\Driver;
use JKingWeb\Arsse\Db\Result; use JKingWeb\Arsse\Db\Result;
use JKingWeb\Arsse\Factory; use JKingWeb\Arsse\Factory;
use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\ValueInfo;
use JKingWeb\Arsse\Misc\URL; use JKingWeb\Arsse\Misc\URL;
use JKingWeb\Arsse\Misc\HTTP;
use Psr\Http\Message\MessageInterface; use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\ServerRequest; use GuzzleHttp\Psr7\ServerRequest;
use Laminas\Diactoros\Response\JsonResponse;
use Laminas\Diactoros\Response\XmlResponse;
/** @coversNothing */ /** @coversNothing */
abstract class AbstractTest extends \PHPUnit\Framework\TestCase { abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
@ -258,7 +256,8 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
} }
} }
$server = array_merge($server, $vars); $server = array_merge($server, $vars);
$req = new ServerRequest($server, [], $url, $method, "php://memory", [], [], $params, $parsedBody); $req = new ServerRequest($method, $url, $headers, $body, "1.1", $server);
$req = $req->withParsedBody($parsedBody)->withQueryParams($params);
if (isset($user)) { if (isset($user)) {
if (strlen($user)) { if (strlen($user)) {
$req = $req->withAttribute("authenticated", true)->withAttribute("authenticatedUser", $user); $req = $req->withAttribute("authenticated", true)->withAttribute("authenticatedUser", $user);
@ -337,12 +336,13 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
$this->assertSame($exp->getMethod(), $act->getMethod(), $text); $this->assertSame($exp->getMethod(), $act->getMethod(), $text);
$this->assertSame($exp->getRequestTarget(), $act->getRequestTarget(), $text); $this->assertSame($exp->getRequestTarget(), $act->getRequestTarget(), $text);
} }
if ($exp instanceof JsonResponse) { if ($exp instanceof ResponseInterface && HTTP::matchType($exp, "application/json", "text/json", "+json")) {
$this->assertInstanceOf(JsonResponse::class, $act, $text); $expBody = @json_decode((string) $exp->getBody(), true);
$this->assertEquals($exp->getPayload(), $act->getPayload(), $text); $actBody = @json_decode((string) $act->getBody(), true);
$this->assertSame($exp->getPayload(), $act->getPayload(), $text); $this->assertSame(\JSON_ERROR_NONE, json_last_error(), "Response body is not valid JSON");
} elseif ($exp instanceof XmlResponse) { $this->assertEquals($expBody, $actBody, $text);
$this->assertInstanceOf(XmlResponse::class, $act, $text); $this->assertSame($expBody, $actBody, $text);
} elseif ($exp instanceof ResponseInterface && HTTP::matchType($exp, "application/xml", "text/xml", "+xml")) {
$this->assertXmlStringEqualsXmlString((string) $exp->getBody(), (string) $act->getBody(), $text); $this->assertXmlStringEqualsXmlString((string) $exp->getBody(), (string) $act->getBody(), $text);
} else { } else {
$this->assertSame((string) $exp->getBody(), (string) $act->getBody(), $text); $this->assertSame((string) $exp->getBody(), (string) $act->getBody(), $text);
@ -350,6 +350,16 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals($exp->getHeaders(), $act->getHeaders(), $text); $this->assertEquals($exp->getHeaders(), $act->getHeaders(), $text);
} }
protected function extractMessageJson(MessageInterface $msg) {
if (HTTP::matchType($msg, "application/json", "text/json", "+json")) {
$json = @json_decode((string) $msg->getBody(), true);
if (json_last_error() === \JSON_ERROR_NONE) {
return $json;
}
}
return null;
}
public function assertTime($exp, $test, string $msg = ''): void { public function assertTime($exp, $test, string $msg = ''): void {
$test = $this->approximateTime($exp, $test); $test = $this->approximateTime($exp, $test);
$exp = Date::transform($exp, "iso8601"); $exp = Date::transform($exp, "iso8601");
@ -388,7 +398,7 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
} }
/** Inserts into the database test data in the following format: /** Inserts into the database test data in the following format:
* *
* ```php * ```php
* $data = [ * $data = [
* 'some_table' => [ * 'some_table' => [
@ -482,7 +492,7 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
// now search for the actual output row in the expected output // now search for the actual output row in the expected output
$found = array_keys($exp, $row, true); $found = array_keys($exp, $row, true);
foreach ($found as $k) { foreach ($found as $k) {
if(!isset($act[$k])) { if (!isset($act[$k])) {
$act[$k] = $row; $act[$k] = $row;
// skip to the next row // skip to the next row
continue 2; continue 2;

1
tests/phpunit.dist.xml

@ -115,7 +115,6 @@
<file>cases/REST/TestREST.php</file> <file>cases/REST/TestREST.php</file>
</testsuite> </testsuite>
<testsuite name="Miniflux"> <testsuite name="Miniflux">
<file>cases/REST/Miniflux/TestErrorResponse.php</file>
<file>cases/REST/Miniflux/TestStatus.php</file> <file>cases/REST/Miniflux/TestStatus.php</file>
<file>cases/REST/Miniflux/TestV1.php</file> <file>cases/REST/Miniflux/TestV1.php</file>
<file>cases/REST/Miniflux/TestToken.php</file> <file>cases/REST/Miniflux/TestToken.php</file>

313
vendor-bin/csfixer/composer.lock

@ -227,16 +227,16 @@
}, },
{ {
"name": "doctrine/annotations", "name": "doctrine/annotations",
"version": "1.13.2", "version": "1.13.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/annotations.git", "url": "https://github.com/doctrine/annotations.git",
"reference": "5b668aef16090008790395c02c893b1ba13f7e08" "reference": "648b0343343565c4a056bfc8392201385e8d89f0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/annotations/zipball/5b668aef16090008790395c02c893b1ba13f7e08", "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0",
"reference": "5b668aef16090008790395c02c893b1ba13f7e08", "reference": "648b0343343565c4a056bfc8392201385e8d89f0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -248,9 +248,10 @@
"require-dev": { "require-dev": {
"doctrine/cache": "^1.11 || ^2.0", "doctrine/cache": "^1.11 || ^2.0",
"doctrine/coding-standard": "^6.0 || ^8.1", "doctrine/coding-standard": "^6.0 || ^8.1",
"phpstan/phpstan": "^0.12.20", "phpstan/phpstan": "^1.4.10 || ^1.8.0",
"phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5",
"symfony/cache": "^4.4 || ^5.2" "symfony/cache": "^4.4 || ^5.2",
"vimeo/psalm": "^4.10"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@ -293,9 +294,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/annotations/issues", "issues": "https://github.com/doctrine/annotations/issues",
"source": "https://github.com/doctrine/annotations/tree/1.13.2" "source": "https://github.com/doctrine/annotations/tree/1.13.3"
}, },
"time": "2021-08-05T19:00:23+00:00" "time": "2022-07-02T10:48:51+00:00"
}, },
{ {
"name": "doctrine/lexer", "name": "doctrine/lexer",
@ -375,16 +376,16 @@
}, },
{ {
"name": "friendsofphp/php-cs-fixer", "name": "friendsofphp/php-cs-fixer",
"version": "v3.8.0", "version": "v3.11.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
"reference": "cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3" "reference": "7dcdea3f2f5f473464e835be9be55283ff8cfdc3"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3", "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/7dcdea3f2f5f473464e835be9be55283ff8cfdc3",
"reference": "cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3", "reference": "7dcdea3f2f5f473464e835be9be55283ff8cfdc3",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -394,7 +395,7 @@
"ext-json": "*", "ext-json": "*",
"ext-tokenizer": "*", "ext-tokenizer": "*",
"php": "^7.4 || ^8.0", "php": "^7.4 || ^8.0",
"php-cs-fixer/diff": "^2.0", "sebastian/diff": "^4.0",
"symfony/console": "^5.4 || ^6.0", "symfony/console": "^5.4 || ^6.0",
"symfony/event-dispatcher": "^5.4 || ^6.0", "symfony/event-dispatcher": "^5.4 || ^6.0",
"symfony/filesystem": "^5.4 || ^6.0", "symfony/filesystem": "^5.4 || ^6.0",
@ -452,7 +453,7 @@
"description": "A tool to automatically fix PHP code style", "description": "A tool to automatically fix PHP code style",
"support": { "support": {
"issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues",
"source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.8.0" "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.11.0"
}, },
"funding": [ "funding": [
{ {
@ -460,59 +461,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2022-03-18T17:20:59+00:00" "time": "2022-09-01T18:24:51+00:00"
},
{
"name": "php-cs-fixer/diff",
"version": "v2.0.2",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/diff.git",
"reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/29dc0d507e838c4580d018bd8b5cb412474f7ec3",
"reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3",
"shasum": ""
},
"require": {
"php": "^5.6 || ^7.0 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0",
"symfony/process": "^3.3"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Kore Nordmann",
"email": "mail@kore-nordmann.de"
}
],
"description": "sebastian/diff v3 backport support for PHP 5.6+",
"homepage": "https://github.com/PHP-CS-Fixer",
"keywords": [
"diff"
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/diff/issues",
"source": "https://github.com/PHP-CS-Fixer/diff/tree/v2.0.2"
},
"time": "2020-10-14T08:32:19+00:00"
}, },
{ {
"name": "psr/cache", "name": "psr/cache",
@ -716,18 +665,84 @@
}, },
"time": "2021-07-14T16:46:02+00:00" "time": "2021-07-14T16:46:02+00:00"
}, },
{
"name": "sebastian/diff",
"version": "4.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d",
"reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d",
"shasum": ""
},
"require": {
"php": ">=7.3"
},
"require-dev": {
"phpunit/phpunit": "^9.3",
"symfony/process": "^4.2 || ^5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Kore Nordmann",
"email": "mail@kore-nordmann.de"
}
],
"description": "Diff implementation",
"homepage": "https://github.com/sebastianbergmann/diff",
"keywords": [
"diff",
"udiff",
"unidiff",
"unified diff"
],
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"source": "https://github.com/sebastianbergmann/diff/tree/4.0.4"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2020-10-26T13:10:38+00:00"
},
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v6.1.0", "version": "v6.1.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "c9646197ef43b0e2ff44af61e7f0571526fd4170" "reference": "7fccea8728aa2d431a6725b02b3ce759049fc84d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/c9646197ef43b0e2ff44af61e7f0571526fd4170", "url": "https://api.github.com/repos/symfony/console/zipball/7fccea8728aa2d431a6725b02b3ce759049fc84d",
"reference": "c9646197ef43b0e2ff44af61e7f0571526fd4170", "reference": "7fccea8728aa2d431a6725b02b3ce759049fc84d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -794,7 +809,7 @@
"terminal" "terminal"
], ],
"support": { "support": {
"source": "https://github.com/symfony/console/tree/v6.1.0" "source": "https://github.com/symfony/console/tree/v6.1.4"
}, },
"funding": [ "funding": [
{ {
@ -810,11 +825,11 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-27T06:34:22+00:00" "time": "2022-08-26T10:32:31+00:00"
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
"version": "v3.1.0", "version": "v3.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git", "url": "https://github.com/symfony/deprecation-contracts.git",
@ -861,7 +876,7 @@
"description": "A generic function and convention to trigger deprecation notices", "description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.0" "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1"
}, },
"funding": [ "funding": [
{ {
@ -964,7 +979,7 @@
}, },
{ {
"name": "symfony/event-dispatcher-contracts", "name": "symfony/event-dispatcher-contracts",
"version": "v3.1.0", "version": "v3.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git", "url": "https://github.com/symfony/event-dispatcher-contracts.git",
@ -1023,7 +1038,7 @@
"standards" "standards"
], ],
"support": { "support": {
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.1.0" "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.1.1"
}, },
"funding": [ "funding": [
{ {
@ -1043,16 +1058,16 @@
}, },
{ {
"name": "symfony/filesystem", "name": "symfony/filesystem",
"version": "v6.1.0", "version": "v6.1.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/filesystem.git", "url": "https://github.com/symfony/filesystem.git",
"reference": "3132d2f43ca799c2aa099f9738d98228c56baa5d" "reference": "3f39c04d2630c34019907b02f85672dac99f8659"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/3132d2f43ca799c2aa099f9738d98228c56baa5d", "url": "https://api.github.com/repos/symfony/filesystem/zipball/3f39c04d2630c34019907b02f85672dac99f8659",
"reference": "3132d2f43ca799c2aa099f9738d98228c56baa5d", "reference": "3f39c04d2630c34019907b02f85672dac99f8659",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1086,7 +1101,7 @@
"description": "Provides basic utilities for the filesystem", "description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/filesystem/tree/v6.1.0" "source": "https://github.com/symfony/filesystem/tree/v6.1.4"
}, },
"funding": [ "funding": [
{ {
@ -1102,20 +1117,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-21T13:34:40+00:00" "time": "2022-08-02T16:17:38+00:00"
}, },
{ {
"name": "symfony/finder", "name": "symfony/finder",
"version": "v6.1.0", "version": "v6.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/finder.git", "url": "https://github.com/symfony/finder.git",
"reference": "45b8beb69d6eb3b05a65689ebfd4222326773f8f" "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/45b8beb69d6eb3b05a65689ebfd4222326773f8f", "url": "https://api.github.com/repos/symfony/finder/zipball/39696bff2c2970b3779a5cac7bf9f0b88fc2b709",
"reference": "45b8beb69d6eb3b05a65689ebfd4222326773f8f", "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1150,7 +1165,7 @@
"description": "Finds files and directories via an intuitive fluent interface", "description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/finder/tree/v6.1.0" "source": "https://github.com/symfony/finder/tree/v6.1.3"
}, },
"funding": [ "funding": [
{ {
@ -1166,7 +1181,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-04-15T08:08:08+00:00" "time": "2022-07-29T07:42:06+00:00"
}, },
{ {
"name": "symfony/options-resolver", "name": "symfony/options-resolver",
@ -1237,16 +1252,16 @@
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git", "url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "30885182c981ab175d4d034db0f6f469898070ab" "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"reference": "30885182c981ab175d4d034db0f6f469898070ab", "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1261,7 +1276,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1299,7 +1314,7 @@
"portable" "portable"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1315,20 +1330,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-10-20T20:35:02+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-intl-grapheme", "name": "symfony/polyfill-intl-grapheme",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "81b86b50cf841a64252b439e738e97f4a34e2783" "reference": "433d05519ce6990bf3530fba6957499d327395c2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2",
"reference": "81b86b50cf841a64252b439e738e97f4a34e2783", "reference": "433d05519ce6990bf3530fba6957499d327395c2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1340,7 +1355,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1380,7 +1395,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1396,20 +1411,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-11-23T21:10:46+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-intl-normalizer", "name": "symfony/polyfill-intl-normalizer",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" "reference": "219aa369ceff116e673852dce47c3a41794c14bd"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd",
"reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", "reference": "219aa369ceff116e673852dce47c3a41794c14bd",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1421,7 +1436,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1464,7 +1479,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1480,20 +1495,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-02-19T12:13:01+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1508,7 +1523,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1547,7 +1562,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1563,20 +1578,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-11-30T18:21:41+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-php80", "name": "symfony/polyfill-php80",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php80.git", "url": "https://github.com/symfony/polyfill-php80.git",
"reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace",
"reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1585,7 +1600,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1630,7 +1645,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1646,20 +1661,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-03-04T08:16:47+00:00" "time": "2022-05-10T07:21:04+00:00"
}, },
{ {
"name": "symfony/polyfill-php81", "name": "symfony/polyfill-php81",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php81.git", "url": "https://github.com/symfony/polyfill-php81.git",
"reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f" "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1",
"reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1668,7 +1683,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1709,7 +1724,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1725,20 +1740,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-09-13T13:58:11+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
"version": "v6.1.0", "version": "v6.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/process.git", "url": "https://github.com/symfony/process.git",
"reference": "318718453c2be58266f1a9e74063d13cb8dd4165" "reference": "a6506e99cfad7059b1ab5cab395854a0a0c21292"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/318718453c2be58266f1a9e74063d13cb8dd4165", "url": "https://api.github.com/repos/symfony/process/zipball/a6506e99cfad7059b1ab5cab395854a0a0c21292",
"reference": "318718453c2be58266f1a9e74063d13cb8dd4165", "reference": "a6506e99cfad7059b1ab5cab395854a0a0c21292",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1770,7 +1785,7 @@
"description": "Executes commands in sub-processes", "description": "Executes commands in sub-processes",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/process/tree/v6.1.0" "source": "https://github.com/symfony/process/tree/v6.1.3"
}, },
"funding": [ "funding": [
{ {
@ -1786,20 +1801,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-11T12:12:29+00:00" "time": "2022-06-27T17:24:16+00:00"
}, },
{ {
"name": "symfony/service-contracts", "name": "symfony/service-contracts",
"version": "v3.1.0", "version": "v3.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/service-contracts.git", "url": "https://github.com/symfony/service-contracts.git",
"reference": "d66cd8ab656780f62c4215b903a420eb86358957" "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/d66cd8ab656780f62c4215b903a420eb86358957", "url": "https://api.github.com/repos/symfony/service-contracts/zipball/925e713fe8fcacf6bc05e936edd8dd5441a21239",
"reference": "d66cd8ab656780f62c4215b903a420eb86358957", "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1855,7 +1870,7 @@
"standards" "standards"
], ],
"support": { "support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.1.0" "source": "https://github.com/symfony/service-contracts/tree/v3.1.1"
}, },
"funding": [ "funding": [
{ {
@ -1871,7 +1886,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-07T08:07:09+00:00" "time": "2022-05-30T19:18:58+00:00"
}, },
{ {
"name": "symfony/stopwatch", "name": "symfony/stopwatch",
@ -1937,16 +1952,16 @@
}, },
{ {
"name": "symfony/string", "name": "symfony/string",
"version": "v6.1.0", "version": "v6.1.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/string.git", "url": "https://github.com/symfony/string.git",
"reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529" "reference": "290972cad7b364e3befaa74ba0ec729800fb161c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/d3edc75baf9f1d4f94879764dda2e1ac33499529", "url": "https://api.github.com/repos/symfony/string/zipball/290972cad7b364e3befaa74ba0ec729800fb161c",
"reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529", "reference": "290972cad7b364e3befaa74ba0ec729800fb161c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2002,7 +2017,7 @@
"utf8" "utf8"
], ],
"support": { "support": {
"source": "https://github.com/symfony/string/tree/v6.1.0" "source": "https://github.com/symfony/string/tree/v6.1.4"
}, },
"funding": [ "funding": [
{ {
@ -2018,7 +2033,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-04-22T08:18:23+00:00" "time": "2022-08-12T18:05:43+00:00"
} }
], ],
"aliases": [], "aliases": [],

277
vendor-bin/daux/composer.lock

@ -83,22 +83,22 @@
}, },
{ {
"name": "guzzlehttp/guzzle", "name": "guzzlehttp/guzzle",
"version": "7.4.3", "version": "7.5.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/guzzle.git", "url": "https://github.com/guzzle/guzzle.git",
"reference": "74a8602c6faec9ef74b7a9391ac82c5e65b1cdab" "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/74a8602c6faec9ef74b7a9391ac82c5e65b1cdab", "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba",
"reference": "74a8602c6faec9ef74b7a9391ac82c5e65b1cdab", "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-json": "*", "ext-json": "*",
"guzzlehttp/promises": "^1.5", "guzzlehttp/promises": "^1.5",
"guzzlehttp/psr7": "^1.8.3 || ^2.1", "guzzlehttp/psr7": "^1.9 || ^2.4",
"php": "^7.2.5 || ^8.0", "php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0", "psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0" "symfony/deprecation-contracts": "^2.2 || ^3.0"
@ -107,10 +107,10 @@
"psr/http-client-implementation": "1.0" "psr/http-client-implementation": "1.0"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.4.1", "bamarni/composer-bin-plugin": "^1.8.1",
"ext-curl": "*", "ext-curl": "*",
"php-http/client-integration-tests": "^3.0", "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" "psr/log": "^1.1 || ^2.0 || ^3.0"
}, },
"suggest": { "suggest": {
@ -120,8 +120,12 @@
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
},
"branch-alias": { "branch-alias": {
"dev-master": "7.4-dev" "dev-master": "7.5-dev"
} }
}, },
"autoload": { "autoload": {
@ -187,7 +191,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/guzzle/issues", "issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.4.3" "source": "https://github.com/guzzle/guzzle/tree/7.5.0"
}, },
"funding": [ "funding": [
{ {
@ -203,20 +207,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-25T13:24:33+00:00" "time": "2022-08-28T15:39:27+00:00"
}, },
{ {
"name": "guzzlehttp/promises", "name": "guzzlehttp/promises",
"version": "1.5.1", "version": "1.5.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/promises.git", "url": "https://github.com/guzzle/promises.git",
"reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" "reference": "b94b2807d85443f9719887892882d0329d1e2598"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598",
"reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", "reference": "b94b2807d85443f9719887892882d0329d1e2598",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -271,7 +275,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/promises/issues", "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": [ "funding": [
{ {
@ -287,20 +291,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-10-22T20:56:57+00:00" "time": "2022-08-28T14:55:35+00:00"
}, },
{ {
"name": "guzzlehttp/psr7", "name": "guzzlehttp/psr7",
"version": "2.2.1", "version": "2.4.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/psr7.git", "url": "https://github.com/guzzle/psr7.git",
"reference": "c94a94f120803a18554c1805ef2e539f8285f9a2" "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/c94a94f120803a18554c1805ef2e539f8285f9a2", "url": "https://api.github.com/repos/guzzle/psr7/zipball/69568e4293f4fa993f3b0e51c9723e1e17c41379",
"reference": "c94a94f120803a18554c1805ef2e539f8285f9a2", "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -314,17 +318,21 @@
"psr/http-message-implementation": "1.0" "psr/http-message-implementation": "1.0"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.4.1", "bamarni/composer-bin-plugin": "^1.8.1",
"http-interop/http-factory-tests": "^0.9", "http-interop/http-factory-tests": "^0.9",
"phpunit/phpunit": "^8.5.8 || ^9.3.10" "phpunit/phpunit": "^8.5.29 || ^9.5.23"
}, },
"suggest": { "suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
},
"branch-alias": { "branch-alias": {
"dev-master": "2.2-dev" "dev-master": "2.4-dev"
} }
}, },
"autoload": { "autoload": {
@ -386,7 +394,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/psr7/issues", "issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.2.1" "source": "https://github.com/guzzle/psr7/tree/2.4.1"
}, },
"funding": [ "funding": [
{ {
@ -402,7 +410,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-03-20T21:55:58+00:00" "time": "2022-08-28T14:45:39+00:00"
}, },
{ {
"name": "league/commonmark", "name": "league/commonmark",
@ -898,16 +906,16 @@
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v5.4.9", "version": "v5.4.12",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb" "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/829d5d1bf60b2efeb0887b7436873becc71a45eb", "url": "https://api.github.com/repos/symfony/console/zipball/c072aa8f724c3af64e2c7a96b796a4863d24dba1",
"reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb", "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -977,7 +985,7 @@
"terminal" "terminal"
], ],
"support": { "support": {
"source": "https://github.com/symfony/console/tree/v5.4.9" "source": "https://github.com/symfony/console/tree/v5.4.12"
}, },
"funding": [ "funding": [
{ {
@ -993,11 +1001,11 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-18T06:17:34+00:00" "time": "2022-08-17T13:18:05+00:00"
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
"version": "v3.1.0", "version": "v3.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git", "url": "https://github.com/symfony/deprecation-contracts.git",
@ -1044,7 +1052,7 @@
"description": "A generic function and convention to trigger deprecation notices", "description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.0" "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1"
}, },
"funding": [ "funding": [
{ {
@ -1064,16 +1072,16 @@
}, },
{ {
"name": "symfony/http-foundation", "name": "symfony/http-foundation",
"version": "v5.4.9", "version": "v5.4.12",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/http-foundation.git", "url": "https://github.com/symfony/http-foundation.git",
"reference": "6b0d0e4aca38d57605dcd11e2416994b38774522" "reference": "f4bfe9611b113b15d98a43da68ec9b5a00d56791"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/6b0d0e4aca38d57605dcd11e2416994b38774522", "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f4bfe9611b113b15d98a43da68ec9b5a00d56791",
"reference": "6b0d0e4aca38d57605dcd11e2416994b38774522", "reference": "f4bfe9611b113b15d98a43da68ec9b5a00d56791",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1085,8 +1093,11 @@
"require-dev": { "require-dev": {
"predis/predis": "~1.0", "predis/predis": "~1.0",
"symfony/cache": "^4.4|^5.0|^6.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/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": { "suggest": {
"symfony/mime": "To use the file extension guesser" "symfony/mime": "To use the file extension guesser"
@ -1117,7 +1128,7 @@
"description": "Defines an object-oriented layer for the HTTP specification", "description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/http-foundation/tree/v5.4.9" "source": "https://github.com/symfony/http-foundation/tree/v5.4.12"
}, },
"funding": [ "funding": [
{ {
@ -1133,20 +1144,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-17T15:07:29+00:00" "time": "2022-08-19T07:33:17+00:00"
}, },
{ {
"name": "symfony/mime", "name": "symfony/mime",
"version": "v5.4.9", "version": "v5.4.12",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/mime.git", "url": "https://github.com/symfony/mime.git",
"reference": "2b3802a24e48d0cfccf885173d2aac91e73df92e" "reference": "03876e9c5a36f5b45e7d9a381edda5421eff8a90"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/mime/zipball/2b3802a24e48d0cfccf885173d2aac91e73df92e", "url": "https://api.github.com/repos/symfony/mime/zipball/03876e9c5a36f5b45e7d9a381edda5421eff8a90",
"reference": "2b3802a24e48d0cfccf885173d2aac91e73df92e", "reference": "03876e9c5a36f5b45e7d9a381edda5421eff8a90",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1200,7 +1211,7 @@
"mime-type" "mime-type"
], ],
"support": { "support": {
"source": "https://github.com/symfony/mime/tree/v5.4.9" "source": "https://github.com/symfony/mime/tree/v5.4.12"
}, },
"funding": [ "funding": [
{ {
@ -1216,20 +1227,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-21T10:24:18+00:00" "time": "2022-08-19T14:24:03+00:00"
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git", "url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "30885182c981ab175d4d034db0f6f469898070ab" "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"reference": "30885182c981ab175d4d034db0f6f469898070ab", "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1244,7 +1255,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1282,7 +1293,7 @@
"portable" "portable"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1298,20 +1309,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-10-20T20:35:02+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-intl-grapheme", "name": "symfony/polyfill-intl-grapheme",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "81b86b50cf841a64252b439e738e97f4a34e2783" "reference": "433d05519ce6990bf3530fba6957499d327395c2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2",
"reference": "81b86b50cf841a64252b439e738e97f4a34e2783", "reference": "433d05519ce6990bf3530fba6957499d327395c2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1323,7 +1334,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1363,7 +1374,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1379,20 +1390,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-11-23T21:10:46+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-intl-icu", "name": "symfony/polyfill-intl-icu",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-icu.git", "url": "https://github.com/symfony/polyfill-intl-icu.git",
"reference": "c023a439b8551e320cc3c8433b198e408a623af1" "reference": "e407643d610e5f2c8a4b14189150f68934bf5e48"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/c023a439b8551e320cc3c8433b198e408a623af1", "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/e407643d610e5f2c8a4b14189150f68934bf5e48",
"reference": "c023a439b8551e320cc3c8433b198e408a623af1", "reference": "e407643d610e5f2c8a4b14189150f68934bf5e48",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1404,7 +1415,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1450,7 +1461,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1466,20 +1477,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-10-26T17:16:04+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-intl-idn", "name": "symfony/polyfill-intl-idn",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git", "url": "https://github.com/symfony/polyfill-intl-idn.git",
"reference": "749045c69efb97c70d25d7463abba812e91f3a44" "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/749045c69efb97c70d25d7463abba812e91f3a44", "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/59a8d271f00dd0e4c2e518104cc7963f655a1aa8",
"reference": "749045c69efb97c70d25d7463abba812e91f3a44", "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1493,7 +1504,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1537,7 +1548,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1553,20 +1564,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-09-14T14:02:44+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-intl-normalizer", "name": "symfony/polyfill-intl-normalizer",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" "reference": "219aa369ceff116e673852dce47c3a41794c14bd"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd",
"reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", "reference": "219aa369ceff116e673852dce47c3a41794c14bd",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1578,7 +1589,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1621,7 +1632,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1637,20 +1648,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-02-19T12:13:01+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1665,7 +1676,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1704,7 +1715,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1720,20 +1731,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-11-30T18:21:41+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-php72", "name": "symfony/polyfill-php72",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php72.git", "url": "https://github.com/symfony/polyfill-php72.git",
"reference": "9a142215a36a3888e30d0a9eeea9766764e96976" "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2",
"reference": "9a142215a36a3888e30d0a9eeea9766764e96976", "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1742,7 +1753,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1780,7 +1791,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-php72/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1796,20 +1807,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-05-27T09:17:38+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-php73", "name": "symfony/polyfill-php73",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php73.git", "url": "https://github.com/symfony/polyfill-php73.git",
"reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5" "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/cc5db0e22b3cb4111010e48785a97f670b350ca5", "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85",
"reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5", "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1818,7 +1829,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1859,7 +1870,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-php73/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1875,20 +1886,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-06-05T21:20:04+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-php80", "name": "symfony/polyfill-php80",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php80.git", "url": "https://github.com/symfony/polyfill-php80.git",
"reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace",
"reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1897,7 +1908,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1942,7 +1953,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1958,20 +1969,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-03-04T08:16:47+00:00" "time": "2022-05-10T07:21:04+00:00"
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
"version": "v5.4.8", "version": "v5.4.11",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/process.git", "url": "https://github.com/symfony/process.git",
"reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3" "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/597f3fff8e3e91836bb0bd38f5718b56ddbde2f3", "url": "https://api.github.com/repos/symfony/process/zipball/6e75fe6874cbc7e4773d049616ab450eff537bf1",
"reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3", "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2004,7 +2015,7 @@
"description": "Executes commands in sub-processes", "description": "Executes commands in sub-processes",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/process/tree/v5.4.8" "source": "https://github.com/symfony/process/tree/v5.4.11"
}, },
"funding": [ "funding": [
{ {
@ -2020,20 +2031,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-04-08T05:07:18+00:00" "time": "2022-06-27T16:58:25+00:00"
}, },
{ {
"name": "symfony/service-contracts", "name": "symfony/service-contracts",
"version": "v3.1.0", "version": "v3.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/service-contracts.git", "url": "https://github.com/symfony/service-contracts.git",
"reference": "d66cd8ab656780f62c4215b903a420eb86358957" "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/d66cd8ab656780f62c4215b903a420eb86358957", "url": "https://api.github.com/repos/symfony/service-contracts/zipball/925e713fe8fcacf6bc05e936edd8dd5441a21239",
"reference": "d66cd8ab656780f62c4215b903a420eb86358957", "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2089,7 +2100,7 @@
"standards" "standards"
], ],
"support": { "support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.1.0" "source": "https://github.com/symfony/service-contracts/tree/v3.1.1"
}, },
"funding": [ "funding": [
{ {
@ -2105,20 +2116,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-07T08:07:09+00:00" "time": "2022-05-30T19:18:58+00:00"
}, },
{ {
"name": "symfony/string", "name": "symfony/string",
"version": "v6.1.0", "version": "v6.1.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/string.git", "url": "https://github.com/symfony/string.git",
"reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529" "reference": "290972cad7b364e3befaa74ba0ec729800fb161c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/d3edc75baf9f1d4f94879764dda2e1ac33499529", "url": "https://api.github.com/repos/symfony/string/zipball/290972cad7b364e3befaa74ba0ec729800fb161c",
"reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529", "reference": "290972cad7b364e3befaa74ba0ec729800fb161c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2174,7 +2185,7 @@
"utf8" "utf8"
], ],
"support": { "support": {
"source": "https://github.com/symfony/string/tree/v6.1.0" "source": "https://github.com/symfony/string/tree/v6.1.4"
}, },
"funding": [ "funding": [
{ {
@ -2190,20 +2201,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-04-22T08:18:23+00:00" "time": "2022-08-12T18:05:43+00:00"
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
"version": "v5.4.3", "version": "v5.4.12",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/yaml.git", "url": "https://github.com/symfony/yaml.git",
"reference": "e80f87d2c9495966768310fc531b487ce64237a2" "reference": "7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/e80f87d2c9495966768310fc531b487ce64237a2", "url": "https://api.github.com/repos/symfony/yaml/zipball/7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c",
"reference": "e80f87d2c9495966768310fc531b487ce64237a2", "reference": "7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2249,7 +2260,7 @@
"description": "Loads and dumps YAML files", "description": "Loads and dumps YAML files",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/yaml/tree/v5.4.3" "source": "https://github.com/symfony/yaml/tree/v5.4.12"
}, },
"funding": [ "funding": [
{ {
@ -2265,7 +2276,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-01-26T16:32:32+00:00" "time": "2022-08-02T15:52:22+00:00"
}, },
{ {
"name": "webuni/front-matter", "name": "webuni/front-matter",

460
vendor-bin/phpunit/composer.lock

@ -338,16 +338,16 @@
}, },
{ {
"name": "mikey179/vfsstream", "name": "mikey179/vfsstream",
"version": "v1.6.10", "version": "v1.6.11",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/bovigo/vfsStream.git", "url": "https://github.com/bovigo/vfsStream.git",
"reference": "250c0825537d501e327df879fb3d4cd751933b85" "reference": "17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/bovigo/vfsStream/zipball/250c0825537d501e327df879fb3d4cd751933b85", "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f",
"reference": "250c0825537d501e327df879fb3d4cd751933b85", "reference": "17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -385,7 +385,7 @@
"source": "https://github.com/bovigo/vfsStream/tree/master", "source": "https://github.com/bovigo/vfsStream/tree/master",
"wiki": "https://github.com/bovigo/vfsStream/wiki" "wiki": "https://github.com/bovigo/vfsStream/wiki"
}, },
"time": "2021-09-25T08:05:01+00:00" "time": "2022-02-23T02:02:42+00:00"
}, },
{ {
"name": "myclabs/deep-copy", "name": "myclabs/deep-copy",
@ -448,16 +448,16 @@
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
"version": "v4.14.0", "version": "v4.15.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nikic/PHP-Parser.git", "url": "https://github.com/nikic/PHP-Parser.git",
"reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900",
"reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -498,9 +498,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/nikic/PHP-Parser/issues", "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", "name": "phar-io/manifest",
@ -613,252 +613,25 @@
}, },
"time": "2022-02-21T01:04:05+00:00" "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", "name": "phpunit/php-code-coverage",
"version": "9.2.15", "version": "9.2.17",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8",
"reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-dom": "*", "ext-dom": "*",
"ext-libxml": "*", "ext-libxml": "*",
"ext-xmlwriter": "*", "ext-xmlwriter": "*",
"nikic/php-parser": "^4.13.0", "nikic/php-parser": "^4.14",
"php": ">=7.3", "php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.3", "phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.2", "phpunit/php-text-template": "^2.0.2",
@ -907,7 +680,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "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": [ "funding": [
{ {
@ -915,7 +688,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2022-03-07T09:28:20+00:00" "time": "2022-08-30T12:24:04+00:00"
}, },
{ {
"name": "phpunit/php-file-iterator", "name": "phpunit/php-file-iterator",
@ -1160,16 +933,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "9.5.20", "version": "9.5.24",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba" "reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/12bc8879fb65aef2138b26fc633cb1e3620cffba", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d0aa6097bef9fd42458a9b3c49da32c6ce6129c5",
"reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba", "reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1184,7 +957,6 @@
"phar-io/manifest": "^2.0.3", "phar-io/manifest": "^2.0.3",
"phar-io/version": "^3.0.2", "phar-io/version": "^3.0.2",
"php": ">=7.3", "php": ">=7.3",
"phpspec/prophecy": "^1.12.1",
"phpunit/php-code-coverage": "^9.2.13", "phpunit/php-code-coverage": "^9.2.13",
"phpunit/php-file-iterator": "^3.0.5", "phpunit/php-file-iterator": "^3.0.5",
"phpunit/php-invoker": "^3.1.1", "phpunit/php-invoker": "^3.1.1",
@ -1199,13 +971,9 @@
"sebastian/global-state": "^5.0.1", "sebastian/global-state": "^5.0.1",
"sebastian/object-enumerator": "^4.0.3", "sebastian/object-enumerator": "^4.0.3",
"sebastian/resource-operations": "^3.0.3", "sebastian/resource-operations": "^3.0.3",
"sebastian/type": "^3.0", "sebastian/type": "^3.1",
"sebastian/version": "^3.0.2" "sebastian/version": "^3.0.2"
}, },
"require-dev": {
"ext-pdo": "*",
"phpspec/prophecy-phpunit": "^2.0.1"
},
"suggest": { "suggest": {
"ext-soap": "*", "ext-soap": "*",
"ext-xdebug": "*" "ext-xdebug": "*"
@ -1247,7 +1015,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.20" "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.24"
}, },
"funding": [ "funding": [
{ {
@ -1259,7 +1027,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2022-04-01T12:37:26+00:00" "time": "2022-08-30T07:42:16+00:00"
}, },
{ {
"name": "sebastian/cli-parser", "name": "sebastian/cli-parser",
@ -1430,16 +1198,16 @@
}, },
{ {
"name": "sebastian/comparator", "name": "sebastian/comparator",
"version": "4.0.6", "version": "4.0.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git", "url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "55f4261989e546dc112258c7a75935a81a7ce382" "reference": "fa0f136dd2334583309d32b62544682ee972b51a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a",
"reference": "55f4261989e546dc112258c7a75935a81a7ce382", "reference": "fa0f136dd2334583309d32b62544682ee972b51a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1492,7 +1260,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues", "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": [ "funding": [
{ {
@ -1500,7 +1268,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2020-10-26T15:49:45+00:00" "time": "2022-09-14T12:41:17+00:00"
}, },
{ {
"name": "sebastian/complexity", "name": "sebastian/complexity",
@ -1690,16 +1458,16 @@
}, },
{ {
"name": "sebastian/exporter", "name": "sebastian/exporter",
"version": "4.0.4", "version": "4.0.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git", "url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
"reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1755,7 +1523,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues", "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": [ "funding": [
{ {
@ -1763,7 +1531,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2021-11-11T14:18:36+00:00" "time": "2022-09-14T06:03:37+00:00"
}, },
{ {
"name": "sebastian/global-state", "name": "sebastian/global-state",
@ -2118,16 +1886,16 @@
}, },
{ {
"name": "sebastian/type", "name": "sebastian/type",
"version": "3.0.0", "version": "3.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/type.git", "url": "https://github.com/sebastianbergmann/type.git",
"reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e",
"reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2139,7 +1907,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "3.0-dev" "dev-master": "3.2-dev"
} }
}, },
"autoload": { "autoload": {
@ -2162,7 +1930,7 @@
"homepage": "https://github.com/sebastianbergmann/type", "homepage": "https://github.com/sebastianbergmann/type",
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/type/issues", "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": [ "funding": [
{ {
@ -2170,7 +1938,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2022-03-15T09:54:48+00:00" "time": "2022-09-12T14:47:03+00:00"
}, },
{ {
"name": "sebastian/version", "name": "sebastian/version",
@ -2225,88 +1993,6 @@
], ],
"time": "2020-09-28T06:39:44+00:00" "time": "2020-09-28T06:39:44+00:00"
}, },
{
"name": "symfony/polyfill-ctype",
"version": "v1.25.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "30885182c981ab175d4d034db0f6f469898070ab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab",
"reference": "30885182c981ab175d4d034db0f6f469898070ab",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-10-20T20:35:02+00:00"
},
{ {
"name": "theseer/tokenizer", "name": "theseer/tokenizer",
"version": "1.2.1", "version": "1.2.1",
@ -2357,64 +2043,6 @@
], ],
"time": "2021-07-28T10:34:58+00:00" "time": "2021-07-28T10:34:58+00:00"
}, },
{
"name": "webmozart/assert",
"version": "1.10.0",
"source": {
"type": "git",
"url": "https://github.com/webmozarts/assert.git",
"reference": "6964c76c7804814a842473e0c8fd15bab0f18e25"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25",
"reference": "6964c76c7804814a842473e0c8fd15bab0f18e25",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"symfony/polyfill-ctype": "^1.8"
},
"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.10.0"
},
"time": "2021-03-09T10:59:23+00:00"
},
{ {
"name": "webmozart/glob", "name": "webmozart/glob",
"version": "4.6.0", "version": "4.6.0",

172
vendor-bin/robo/composer.lock

@ -90,16 +90,16 @@
}, },
{ {
"name": "consolidation/annotated-command", "name": "consolidation/annotated-command",
"version": "4.5.5", "version": "4.5.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/consolidation/annotated-command.git", "url": "https://github.com/consolidation/annotated-command.git",
"reference": "67cea8e8e7656b74da651ea6f49321853996c0fd" "reference": "3968070538761628546270935f0733a0cc408e1f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/consolidation/annotated-command/zipball/67cea8e8e7656b74da651ea6f49321853996c0fd", "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/3968070538761628546270935f0733a0cc408e1f",
"reference": "67cea8e8e7656b74da651ea6f49321853996c0fd", "reference": "3968070538761628546270935f0733a0cc408e1f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -140,22 +140,22 @@
"description": "Initialize Symfony Console commands from annotated command class methods.", "description": "Initialize Symfony Console commands from annotated command class methods.",
"support": { "support": {
"issues": "https://github.com/consolidation/annotated-command/issues", "issues": "https://github.com/consolidation/annotated-command/issues",
"source": "https://github.com/consolidation/annotated-command/tree/4.5.5" "source": "https://github.com/consolidation/annotated-command/tree/4.5.6"
}, },
"time": "2022-04-26T16:18:25+00:00" "time": "2022-06-22T20:17:12+00:00"
}, },
{ {
"name": "consolidation/config", "name": "consolidation/config",
"version": "2.1.0", "version": "2.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/consolidation/config.git", "url": "https://github.com/consolidation/config.git",
"reference": "0c15841b2bf60d9af1ce29884673e7d9d50c3b75" "reference": "dae810c162f0e799ea3f35cc2f40b0797b6e4d26"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/consolidation/config/zipball/0c15841b2bf60d9af1ce29884673e7d9d50c3b75", "url": "https://api.github.com/repos/consolidation/config/zipball/dae810c162f0e799ea3f35cc2f40b0797b6e4d26",
"reference": "0c15841b2bf60d9af1ce29884673e7d9d50c3b75", "reference": "dae810c162f0e799ea3f35cc2f40b0797b6e4d26",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -200,9 +200,9 @@
"description": "Provide configuration services for a commandline tool.", "description": "Provide configuration services for a commandline tool.",
"support": { "support": {
"issues": "https://github.com/consolidation/config/issues", "issues": "https://github.com/consolidation/config/issues",
"source": "https://github.com/consolidation/config/tree/2.1.0" "source": "https://github.com/consolidation/config/tree/2.1.1"
}, },
"time": "2022-02-24T00:32:42+00:00" "time": "2022-06-22T19:59:34+00:00"
}, },
{ {
"name": "consolidation/log", "name": "consolidation/log",
@ -1070,16 +1070,16 @@
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v6.1.0", "version": "v6.1.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "c9646197ef43b0e2ff44af61e7f0571526fd4170" "reference": "7fccea8728aa2d431a6725b02b3ce759049fc84d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/c9646197ef43b0e2ff44af61e7f0571526fd4170", "url": "https://api.github.com/repos/symfony/console/zipball/7fccea8728aa2d431a6725b02b3ce759049fc84d",
"reference": "c9646197ef43b0e2ff44af61e7f0571526fd4170", "reference": "7fccea8728aa2d431a6725b02b3ce759049fc84d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1146,7 +1146,7 @@
"terminal" "terminal"
], ],
"support": { "support": {
"source": "https://github.com/symfony/console/tree/v6.1.0" "source": "https://github.com/symfony/console/tree/v6.1.4"
}, },
"funding": [ "funding": [
{ {
@ -1162,11 +1162,11 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-27T06:34:22+00:00" "time": "2022-08-26T10:32:31+00:00"
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
"version": "v3.1.0", "version": "v3.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git", "url": "https://github.com/symfony/deprecation-contracts.git",
@ -1213,7 +1213,7 @@
"description": "A generic function and convention to trigger deprecation notices", "description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.0" "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1"
}, },
"funding": [ "funding": [
{ {
@ -1316,7 +1316,7 @@
}, },
{ {
"name": "symfony/event-dispatcher-contracts", "name": "symfony/event-dispatcher-contracts",
"version": "v3.1.0", "version": "v3.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git", "url": "https://github.com/symfony/event-dispatcher-contracts.git",
@ -1375,7 +1375,7 @@
"standards" "standards"
], ],
"support": { "support": {
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.1.0" "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.1.1"
}, },
"funding": [ "funding": [
{ {
@ -1395,16 +1395,16 @@
}, },
{ {
"name": "symfony/filesystem", "name": "symfony/filesystem",
"version": "v6.1.0", "version": "v6.1.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/filesystem.git", "url": "https://github.com/symfony/filesystem.git",
"reference": "3132d2f43ca799c2aa099f9738d98228c56baa5d" "reference": "3f39c04d2630c34019907b02f85672dac99f8659"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/3132d2f43ca799c2aa099f9738d98228c56baa5d", "url": "https://api.github.com/repos/symfony/filesystem/zipball/3f39c04d2630c34019907b02f85672dac99f8659",
"reference": "3132d2f43ca799c2aa099f9738d98228c56baa5d", "reference": "3f39c04d2630c34019907b02f85672dac99f8659",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1438,7 +1438,7 @@
"description": "Provides basic utilities for the filesystem", "description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/filesystem/tree/v6.1.0" "source": "https://github.com/symfony/filesystem/tree/v6.1.4"
}, },
"funding": [ "funding": [
{ {
@ -1454,20 +1454,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-21T13:34:40+00:00" "time": "2022-08-02T16:17:38+00:00"
}, },
{ {
"name": "symfony/finder", "name": "symfony/finder",
"version": "v6.1.0", "version": "v6.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/finder.git", "url": "https://github.com/symfony/finder.git",
"reference": "45b8beb69d6eb3b05a65689ebfd4222326773f8f" "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/45b8beb69d6eb3b05a65689ebfd4222326773f8f", "url": "https://api.github.com/repos/symfony/finder/zipball/39696bff2c2970b3779a5cac7bf9f0b88fc2b709",
"reference": "45b8beb69d6eb3b05a65689ebfd4222326773f8f", "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1502,7 +1502,7 @@
"description": "Finds files and directories via an intuitive fluent interface", "description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/finder/tree/v6.1.0" "source": "https://github.com/symfony/finder/tree/v6.1.3"
}, },
"funding": [ "funding": [
{ {
@ -1518,20 +1518,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-04-15T08:08:08+00:00" "time": "2022-07-29T07:42:06+00:00"
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git", "url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "30885182c981ab175d4d034db0f6f469898070ab" "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"reference": "30885182c981ab175d4d034db0f6f469898070ab", "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1546,7 +1546,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1584,7 +1584,7 @@
"portable" "portable"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1600,20 +1600,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-10-20T20:35:02+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-intl-grapheme", "name": "symfony/polyfill-intl-grapheme",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "81b86b50cf841a64252b439e738e97f4a34e2783" "reference": "433d05519ce6990bf3530fba6957499d327395c2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2",
"reference": "81b86b50cf841a64252b439e738e97f4a34e2783", "reference": "433d05519ce6990bf3530fba6957499d327395c2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1625,7 +1625,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1665,7 +1665,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1681,20 +1681,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-11-23T21:10:46+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-intl-normalizer", "name": "symfony/polyfill-intl-normalizer",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" "reference": "219aa369ceff116e673852dce47c3a41794c14bd"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd",
"reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", "reference": "219aa369ceff116e673852dce47c3a41794c14bd",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1706,7 +1706,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1749,7 +1749,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1765,20 +1765,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-02-19T12:13:01+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.25.0", "version": "v1.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1793,7 +1793,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.23-dev" "dev-main": "1.26-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@ -1832,7 +1832,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0"
}, },
"funding": [ "funding": [
{ {
@ -1848,20 +1848,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-11-30T18:21:41+00:00" "time": "2022-05-24T11:49:31+00:00"
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
"version": "v6.1.0", "version": "v6.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/process.git", "url": "https://github.com/symfony/process.git",
"reference": "318718453c2be58266f1a9e74063d13cb8dd4165" "reference": "a6506e99cfad7059b1ab5cab395854a0a0c21292"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/318718453c2be58266f1a9e74063d13cb8dd4165", "url": "https://api.github.com/repos/symfony/process/zipball/a6506e99cfad7059b1ab5cab395854a0a0c21292",
"reference": "318718453c2be58266f1a9e74063d13cb8dd4165", "reference": "a6506e99cfad7059b1ab5cab395854a0a0c21292",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1893,7 +1893,7 @@
"description": "Executes commands in sub-processes", "description": "Executes commands in sub-processes",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/process/tree/v6.1.0" "source": "https://github.com/symfony/process/tree/v6.1.3"
}, },
"funding": [ "funding": [
{ {
@ -1909,20 +1909,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-11T12:12:29+00:00" "time": "2022-06-27T17:24:16+00:00"
}, },
{ {
"name": "symfony/service-contracts", "name": "symfony/service-contracts",
"version": "v3.1.0", "version": "v3.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/service-contracts.git", "url": "https://github.com/symfony/service-contracts.git",
"reference": "d66cd8ab656780f62c4215b903a420eb86358957" "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/d66cd8ab656780f62c4215b903a420eb86358957", "url": "https://api.github.com/repos/symfony/service-contracts/zipball/925e713fe8fcacf6bc05e936edd8dd5441a21239",
"reference": "d66cd8ab656780f62c4215b903a420eb86358957", "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1978,7 +1978,7 @@
"standards" "standards"
], ],
"support": { "support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.1.0" "source": "https://github.com/symfony/service-contracts/tree/v3.1.1"
}, },
"funding": [ "funding": [
{ {
@ -1994,20 +1994,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-05-07T08:07:09+00:00" "time": "2022-05-30T19:18:58+00:00"
}, },
{ {
"name": "symfony/string", "name": "symfony/string",
"version": "v6.1.0", "version": "v6.1.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/string.git", "url": "https://github.com/symfony/string.git",
"reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529" "reference": "290972cad7b364e3befaa74ba0ec729800fb161c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/d3edc75baf9f1d4f94879764dda2e1ac33499529", "url": "https://api.github.com/repos/symfony/string/zipball/290972cad7b364e3befaa74ba0ec729800fb161c",
"reference": "d3edc75baf9f1d4f94879764dda2e1ac33499529", "reference": "290972cad7b364e3befaa74ba0ec729800fb161c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2063,7 +2063,7 @@
"utf8" "utf8"
], ],
"support": { "support": {
"source": "https://github.com/symfony/string/tree/v6.1.0" "source": "https://github.com/symfony/string/tree/v6.1.4"
}, },
"funding": [ "funding": [
{ {
@ -2079,20 +2079,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-04-22T08:18:23+00:00" "time": "2022-08-12T18:05:43+00:00"
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
"version": "v6.1.0", "version": "v6.1.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/yaml.git", "url": "https://github.com/symfony/yaml.git",
"reference": "84ce4f9d2d68f306f971a39d949d8f4b5550dba2" "reference": "86ee4d8fa594ed45e40d86eedfda1bcb66c8d919"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/84ce4f9d2d68f306f971a39d949d8f4b5550dba2", "url": "https://api.github.com/repos/symfony/yaml/zipball/86ee4d8fa594ed45e40d86eedfda1bcb66c8d919",
"reference": "84ce4f9d2d68f306f971a39d949d8f4b5550dba2", "reference": "86ee4d8fa594ed45e40d86eedfda1bcb66c8d919",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2137,7 +2137,7 @@
"description": "Loads and dumps YAML files", "description": "Loads and dumps YAML files",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/yaml/tree/v6.1.0" "source": "https://github.com/symfony/yaml/tree/v6.1.4"
}, },
"funding": [ "funding": [
{ {
@ -2153,7 +2153,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-04-15T14:25:02+00:00" "time": "2022-08-02T16:17:38+00:00"
} }
], ],
"aliases": [], "aliases": [],

Loading…
Cancel
Save