You've already forked Php-Template
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
e9d4cee336
|
|||
|
7d9eb96bea
|
|||
|
9b736eb879
|
|||
|
7aa14c0db3
|
|||
|
474134c654
|
@@ -38,7 +38,7 @@ jobs:
|
||||
-e POSTGRES_PASSWORD=postgres \
|
||||
-e POSTGRES_DB=postgres \
|
||||
-p 5432 \
|
||||
-d postgres:17
|
||||
-d postgres:18
|
||||
|
||||
echo "Waiting for Postgres to start"
|
||||
sleep 10
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<option name="interpreterName" value="composer-runtime" />
|
||||
</PhpTestInterpreterSettings>
|
||||
</CommandLine>
|
||||
<TestRunner configuration_file="$PROJECT_DIR$/phpunit.xml" scope="XML" use_alternative_configuration_file="true" />
|
||||
<TestRunner configuration_file="$PROJECT_DIR$/phpunit.xml" coverage_engine="PCov" scope="XML" use_alternative_configuration_file="true" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -21,12 +21,15 @@
|
||||
"lcobucci/jwt": "^5.6",
|
||||
"adhocore/cli": "^1.9",
|
||||
"robinvdvleuten/ulid": "^5.0",
|
||||
"monolog/monolog": "^3.9"
|
||||
"monolog/monolog": "^3.9",
|
||||
"react/promise": "^3",
|
||||
"react/async": "^4",
|
||||
"guzzlehttp/guzzle": "^7.10"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^12.4",
|
||||
"mockery/mockery": "^1.6",
|
||||
"squizlabs/php_codesniffer": "^3.12",
|
||||
"squizlabs/php_codesniffer": "^4.0",
|
||||
"lendable/composer-license-checker": "^1.2",
|
||||
"phpstan/phpstan": "^2.1.31",
|
||||
"kwn/php-rdkafka-stubs": "^2.2"
|
||||
|
||||
726
composer.lock
generated
726
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "7c2d40400d6f4d0469324dc1645eba3c",
|
||||
"content-hash": "8c2444c3a25a3469cf369de1c085ad01",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/cli",
|
||||
@@ -300,16 +300,16 @@
|
||||
},
|
||||
{
|
||||
"name": "google/protobuf",
|
||||
"version": "v4.33.0",
|
||||
"version": "v4.33.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/protocolbuffers/protobuf-php.git",
|
||||
"reference": "b50269e23204e5ae859a326ec3d90f09efe3047d"
|
||||
"reference": "0cd73ccf0cd26c3e72299cce1ea6144091a57e12"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/b50269e23204e5ae859a326ec3d90f09efe3047d",
|
||||
"reference": "b50269e23204e5ae859a326ec3d90f09efe3047d",
|
||||
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/0cd73ccf0cd26c3e72299cce1ea6144091a57e12",
|
||||
"reference": "0cd73ccf0cd26c3e72299cce1ea6144091a57e12",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -338,13 +338,338 @@
|
||||
"proto"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.0"
|
||||
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.1"
|
||||
},
|
||||
"time": "2025-10-15T20:10:28+00:00"
|
||||
"time": "2025-11-12T21:58:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "7.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/guzzle.git",
|
||||
"reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4",
|
||||
"reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/promises": "^2.3",
|
||||
"guzzlehttp/psr7": "^2.8",
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"symfony/deprecation-contracts": "^2.2 || ^3.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/http-client-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"ext-curl": "*",
|
||||
"guzzle/client-integration-tests": "3.0.2",
|
||||
"php-http/message-factory": "^1.1",
|
||||
"phpunit/phpunit": "^8.5.39 || ^9.6.20",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "Required for CURL handler support",
|
||||
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
|
||||
"psr/log": "Required for using the Log middleware"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Jeremy Lindblom",
|
||||
"email": "jeremeamia@gmail.com",
|
||||
"homepage": "https://github.com/jeremeamia"
|
||||
},
|
||||
{
|
||||
"name": "George Mponos",
|
||||
"email": "gmponos@gmail.com",
|
||||
"homepage": "https://github.com/gmponos"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com",
|
||||
"homepage": "https://github.com/Nyholm"
|
||||
},
|
||||
{
|
||||
"name": "Márk Sági-Kazár",
|
||||
"email": "mark.sagikazar@gmail.com",
|
||||
"homepage": "https://github.com/sagikazarmark"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"email": "webmaster@tubo-world.de",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
}
|
||||
],
|
||||
"description": "Guzzle is a PHP HTTP client library",
|
||||
"keywords": [
|
||||
"client",
|
||||
"curl",
|
||||
"framework",
|
||||
"http",
|
||||
"http client",
|
||||
"psr-18",
|
||||
"psr-7",
|
||||
"rest",
|
||||
"web service"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/guzzle/issues",
|
||||
"source": "https://github.com/guzzle/guzzle/tree/7.10.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Nyholm",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-23T22:36:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/promises",
|
||||
"version": "2.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/promises.git",
|
||||
"reference": "481557b130ef3790cf82b713667b43030dc9c957"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957",
|
||||
"reference": "481557b130ef3790cf82b713667b43030dc9c957",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"phpunit/phpunit": "^8.5.44 || ^9.6.25"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Promise\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com",
|
||||
"homepage": "https://github.com/Nyholm"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"email": "webmaster@tubo-world.de",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
}
|
||||
],
|
||||
"description": "Guzzle promises library",
|
||||
"keywords": [
|
||||
"promise"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/promises/issues",
|
||||
"source": "https://github.com/guzzle/promises/tree/2.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Nyholm",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-22T14:34:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/psr7",
|
||||
"version": "2.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/psr7.git",
|
||||
"reference": "21dc724a0583619cd1652f673303492272778051"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051",
|
||||
"reference": "21dc724a0583619cd1652f673303492272778051",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/http-message": "^1.1 || ^2.0",
|
||||
"ralouphie/getallheaders": "^3.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/http-factory-implementation": "1.0",
|
||||
"psr/http-message-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"http-interop/http-factory-tests": "0.9.0",
|
||||
"phpunit/phpunit": "^8.5.44 || ^9.6.25"
|
||||
},
|
||||
"suggest": {
|
||||
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Psr7\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "George Mponos",
|
||||
"email": "gmponos@gmail.com",
|
||||
"homepage": "https://github.com/gmponos"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com",
|
||||
"homepage": "https://github.com/Nyholm"
|
||||
},
|
||||
{
|
||||
"name": "Márk Sági-Kazár",
|
||||
"email": "mark.sagikazar@gmail.com",
|
||||
"homepage": "https://github.com/sagikazarmark"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"email": "webmaster@tubo-world.de",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
},
|
||||
{
|
||||
"name": "Márk Sági-Kazár",
|
||||
"email": "mark.sagikazar@gmail.com",
|
||||
"homepage": "https://sagikazarmark.hu"
|
||||
}
|
||||
],
|
||||
"description": "PSR-7 message implementation that also provides common utility methods",
|
||||
"keywords": [
|
||||
"http",
|
||||
"message",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response",
|
||||
"stream",
|
||||
"uri",
|
||||
"url"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/psr7/issues",
|
||||
"source": "https://github.com/guzzle/psr7/tree/2.8.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Nyholm",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-23T21:21:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/collections",
|
||||
"version": "v12.38.0",
|
||||
"version": "v12.38.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/collections.git",
|
||||
@@ -403,7 +728,7 @@
|
||||
},
|
||||
{
|
||||
"name": "illuminate/conditionable",
|
||||
"version": "v12.38.0",
|
||||
"version": "v12.38.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/conditionable.git",
|
||||
@@ -449,7 +774,7 @@
|
||||
},
|
||||
{
|
||||
"name": "illuminate/container",
|
||||
"version": "v12.38.0",
|
||||
"version": "v12.38.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/container.git",
|
||||
@@ -510,7 +835,7 @@
|
||||
},
|
||||
{
|
||||
"name": "illuminate/contracts",
|
||||
"version": "v12.38.0",
|
||||
"version": "v12.38.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/contracts.git",
|
||||
@@ -558,7 +883,7 @@
|
||||
},
|
||||
{
|
||||
"name": "illuminate/database",
|
||||
"version": "v12.38.0",
|
||||
"version": "v12.38.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/database.git",
|
||||
@@ -629,7 +954,7 @@
|
||||
},
|
||||
{
|
||||
"name": "illuminate/macroable",
|
||||
"version": "v12.38.0",
|
||||
"version": "v12.38.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/macroable.git",
|
||||
@@ -675,7 +1000,7 @@
|
||||
},
|
||||
{
|
||||
"name": "illuminate/support",
|
||||
"version": "v12.38.0",
|
||||
"version": "v12.38.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/support.git",
|
||||
@@ -1476,6 +1801,58 @@
|
||||
},
|
||||
"time": "2021-11-05T16:47:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-client",
|
||||
"version": "1.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-client.git",
|
||||
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
|
||||
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.0 || ^8.0",
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Client\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP clients",
|
||||
"homepage": "https://github.com/php-fig/http-client",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-client",
|
||||
"psr",
|
||||
"psr-18"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-client"
|
||||
},
|
||||
"time": "2023-09-23T14:17:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-factory",
|
||||
"version": "1.1.0",
|
||||
@@ -1798,6 +2175,270 @@
|
||||
},
|
||||
"time": "2021-10-29T13:26:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ralouphie/getallheaders",
|
||||
"version": "3.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ralouphie/getallheaders.git",
|
||||
"reference": "120b605dfeb996808c31b6477290a714d356e822"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
|
||||
"reference": "120b605dfeb996808c31b6477290a714d356e822",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-coveralls/php-coveralls": "^2.1",
|
||||
"phpunit/phpunit": "^5 || ^6.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/getallheaders.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ralph Khattar",
|
||||
"email": "ralph.khattar@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A polyfill for getallheaders.",
|
||||
"support": {
|
||||
"issues": "https://github.com/ralouphie/getallheaders/issues",
|
||||
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
|
||||
},
|
||||
"time": "2019-03-08T08:55:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "react/async",
|
||||
"version": "v4.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactphp/async.git",
|
||||
"reference": "635d50e30844a484495713e8cb8d9e079c0008a5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/reactphp/async/zipball/635d50e30844a484495713e8cb8d9e079c0008a5",
|
||||
"reference": "635d50e30844a484495713e8cb8d9e079c0008a5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/promise": "^3.2 || ^2.8 || ^1.2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.10.39",
|
||||
"phpunit/phpunit": "^9.6"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"React\\Async\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering",
|
||||
"homepage": "https://clue.engineering/"
|
||||
},
|
||||
{
|
||||
"name": "Cees-Jan Kiewiet",
|
||||
"email": "reactphp@ceesjankiewiet.nl",
|
||||
"homepage": "https://wyrihaximus.net/"
|
||||
},
|
||||
{
|
||||
"name": "Jan Sorgalla",
|
||||
"email": "jsorgalla@gmail.com",
|
||||
"homepage": "https://sorgalla.com/"
|
||||
},
|
||||
{
|
||||
"name": "Chris Boden",
|
||||
"email": "cboden@gmail.com",
|
||||
"homepage": "https://cboden.dev/"
|
||||
}
|
||||
],
|
||||
"description": "Async utilities and fibers for ReactPHP",
|
||||
"keywords": [
|
||||
"async",
|
||||
"reactphp"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/reactphp/async/issues",
|
||||
"source": "https://github.com/reactphp/async/tree/v4.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://opencollective.com/reactphp",
|
||||
"type": "open_collective"
|
||||
}
|
||||
],
|
||||
"time": "2024-06-04T14:40:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "react/event-loop",
|
||||
"version": "v1.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactphp/event-loop.git",
|
||||
"reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354",
|
||||
"reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-pcntl": "For signal handling support when using the StreamSelectLoop"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\EventLoop\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering",
|
||||
"homepage": "https://clue.engineering/"
|
||||
},
|
||||
{
|
||||
"name": "Cees-Jan Kiewiet",
|
||||
"email": "reactphp@ceesjankiewiet.nl",
|
||||
"homepage": "https://wyrihaximus.net/"
|
||||
},
|
||||
{
|
||||
"name": "Jan Sorgalla",
|
||||
"email": "jsorgalla@gmail.com",
|
||||
"homepage": "https://sorgalla.com/"
|
||||
},
|
||||
{
|
||||
"name": "Chris Boden",
|
||||
"email": "cboden@gmail.com",
|
||||
"homepage": "https://cboden.dev/"
|
||||
}
|
||||
],
|
||||
"description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
|
||||
"keywords": [
|
||||
"asynchronous",
|
||||
"event-loop"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/reactphp/event-loop/issues",
|
||||
"source": "https://github.com/reactphp/event-loop/tree/v1.5.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://opencollective.com/reactphp",
|
||||
"type": "open_collective"
|
||||
}
|
||||
],
|
||||
"time": "2023-11-13T13:48:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "react/promise",
|
||||
"version": "v3.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactphp/promise.git",
|
||||
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a",
|
||||
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.12.28 || 1.4.10",
|
||||
"phpunit/phpunit": "^9.6 || ^7.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"React\\Promise\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jan Sorgalla",
|
||||
"email": "jsorgalla@gmail.com",
|
||||
"homepage": "https://sorgalla.com/"
|
||||
},
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering",
|
||||
"homepage": "https://clue.engineering/"
|
||||
},
|
||||
{
|
||||
"name": "Cees-Jan Kiewiet",
|
||||
"email": "reactphp@ceesjankiewiet.nl",
|
||||
"homepage": "https://wyrihaximus.net/"
|
||||
},
|
||||
{
|
||||
"name": "Chris Boden",
|
||||
"email": "cboden@gmail.com",
|
||||
"homepage": "https://cboden.dev/"
|
||||
}
|
||||
],
|
||||
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
|
||||
"keywords": [
|
||||
"promise",
|
||||
"promises"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/reactphp/promise/issues",
|
||||
"source": "https://github.com/reactphp/promise/tree/v3.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://opencollective.com/reactphp",
|
||||
"type": "open_collective"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-19T18:57:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "roadrunner-php/app-logger",
|
||||
"version": "1.2.0",
|
||||
@@ -2150,16 +2791,16 @@
|
||||
},
|
||||
{
|
||||
"name": "spiral/roadrunner",
|
||||
"version": "v2025.1.4",
|
||||
"version": "v2025.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/roadrunner-server/roadrunner.git",
|
||||
"reference": "ff25363b72dd6ab2bd642d741db13b30d52d254f"
|
||||
"reference": "d68bee29eb689c5310f8ede935c95a13bd7cc153"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/roadrunner-server/roadrunner/zipball/ff25363b72dd6ab2bd642d741db13b30d52d254f",
|
||||
"reference": "ff25363b72dd6ab2bd642d741db13b30d52d254f",
|
||||
"url": "https://api.github.com/repos/roadrunner-server/roadrunner/zipball/d68bee29eb689c5310f8ede935c95a13bd7cc153",
|
||||
"reference": "d68bee29eb689c5310f8ede935c95a13bd7cc153",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "metapackage",
|
||||
@@ -2185,10 +2826,9 @@
|
||||
"homepage": "https://roadrunner.dev/",
|
||||
"support": {
|
||||
"chat": "https://discord.gg/V6EK4he",
|
||||
"docs": "https://roadrunner.dev/docs",
|
||||
"forum": "https://forum.roadrunner.dev/",
|
||||
"docs": "https://docs.roadrunner.dev/",
|
||||
"issues": "https://github.com/roadrunner-server/roadrunner/issues",
|
||||
"source": "https://github.com/roadrunner-server/roadrunner/tree/v2025.1.4"
|
||||
"source": "https://github.com/roadrunner-server/roadrunner/tree/v2025.1.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2196,7 +2836,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-10-02T16:43:06+00:00"
|
||||
"time": "2025-11-13T17:24:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spiral/roadrunner-http",
|
||||
@@ -3952,16 +4592,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "12.4.2",
|
||||
"version": "12.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "a94ea4d26d865875803b23aaf78c3c2c670ea2ea"
|
||||
"reference": "d8f644d8d9bb904867f7a0aeb1bd306e0d966949"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a94ea4d26d865875803b23aaf78c3c2c670ea2ea",
|
||||
"reference": "a94ea4d26d865875803b23aaf78c3c2c670ea2ea",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d8f644d8d9bb904867f7a0aeb1bd306e0d966949",
|
||||
"reference": "d8f644d8d9bb904867f7a0aeb1bd306e0d966949",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4029,7 +4669,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.4.2"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.4.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -4053,7 +4693,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-10-30T08:41:39+00:00"
|
||||
"time": "2025-11-13T07:20:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
@@ -4954,26 +5594,26 @@
|
||||
},
|
||||
{
|
||||
"name": "squizlabs/php_codesniffer",
|
||||
"version": "3.13.5",
|
||||
"version": "4.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
|
||||
"reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4"
|
||||
"reference": "0525c73950de35ded110cffafb9892946d7771b5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4",
|
||||
"reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4",
|
||||
"url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5",
|
||||
"reference": "0525c73950de35ded110cffafb9892946d7771b5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-simplexml": "*",
|
||||
"ext-tokenizer": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"php": ">=5.4.0"
|
||||
"php": ">=7.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
|
||||
"phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31"
|
||||
},
|
||||
"bin": [
|
||||
"bin/phpcbf",
|
||||
@@ -4998,7 +5638,7 @@
|
||||
"homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors"
|
||||
}
|
||||
],
|
||||
"description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
|
||||
"description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.",
|
||||
"homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
|
||||
"keywords": [
|
||||
"phpcs",
|
||||
@@ -5029,7 +5669,7 @@
|
||||
"type": "thanks_dev"
|
||||
}
|
||||
],
|
||||
"time": "2025-11-04T16:30:35+00:00"
|
||||
"time": "2025-11-10T16:43:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "staabm/side-effects-detector",
|
||||
@@ -5675,16 +6315,16 @@
|
||||
},
|
||||
{
|
||||
"name": "theseer/tokenizer",
|
||||
"version": "1.2.3",
|
||||
"version": "1.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/theseer/tokenizer.git",
|
||||
"reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
|
||||
"reference": "d74205c497bfbca49f34d4bc4c19c17e22db4ebb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
|
||||
"reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
|
||||
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/d74205c497bfbca49f34d4bc4c19c17e22db4ebb",
|
||||
"reference": "d74205c497bfbca49f34d4bc4c19c17e22db4ebb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5713,7 +6353,7 @@
|
||||
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
|
||||
"support": {
|
||||
"issues": "https://github.com/theseer/tokenizer/issues",
|
||||
"source": "https://github.com/theseer/tokenizer/tree/1.2.3"
|
||||
"source": "https://github.com/theseer/tokenizer/tree/1.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -5721,7 +6361,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-03-03T12:36:25+00:00"
|
||||
"time": "2025-11-13T13:44:09+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
@@ -5733,5 +6373,5 @@
|
||||
"php": "^8.4"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
"plugin-api-version": "2.9.0"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ use Siteworxpro\App\Helpers\Env;
|
||||
|
||||
return [
|
||||
|
||||
'app' => [
|
||||
'log_level' => Env::get('LOG_LEVEL', 'debug'),
|
||||
],
|
||||
|
||||
/**
|
||||
* The server configuration.
|
||||
*/
|
||||
@@ -47,7 +51,7 @@ return [
|
||||
'signing_key' => Env::get('JWT_SIGNING_KEY', 'a_super_secret_key'),
|
||||
'audience' => Env::get('JWT_AUDIENCE', 'my_audience'),
|
||||
'issuer' => Env::get('JWT_ISSUER', 'my_issuer'),
|
||||
'strict_validation' => Env::get('JWT_STRICT_VALIDATION', true, 'bool'),
|
||||
'strict_validation' => Env::get('JWT_STRICT_VALIDATION', false, 'bool'),
|
||||
],
|
||||
|
||||
'queue' => [
|
||||
|
||||
@@ -83,13 +83,15 @@ services:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
JWT_ISSUER: https://auth.siteworxpro.com/application/o/postman/
|
||||
JWT_AUDIENCE: 1RWyqJFlyA4hmsDzq6kSxs0LXvk7UgEAfgmBCpQ9
|
||||
JWT_SIGNING_KEY: https://auth.siteworxpro.com/application/o/postman/.well-known/openid-configuration
|
||||
QUEUE_BROKER: redis
|
||||
PHP_IDE_CONFIG: serverName=localhost
|
||||
WORKERS: 1
|
||||
DEBUG: 1
|
||||
REDIS_HOST: redis
|
||||
DB_HOST: postgres
|
||||
JWT_SIGNING_KEY: a-string-secret-at-least-256-bits-long
|
||||
|
||||
## Kafka and Zookeeper for local development
|
||||
kafka-ui:
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Annotations\Guards;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
|
||||
readonly class Scope
|
||||
{
|
||||
public function __construct(
|
||||
private array $scopes = []
|
||||
) {
|
||||
}
|
||||
|
||||
public function getScopes(): array
|
||||
{
|
||||
return $this->scopes;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ declare(ticks=1);
|
||||
|
||||
namespace Siteworxpro\App\Async;
|
||||
|
||||
use Siteworxpro\App\Annotations\Async\HandlesMessage;
|
||||
use Siteworxpro\App\Attributes\Async\HandlesMessage;
|
||||
use Siteworxpro\App\Async\Messages\Message;
|
||||
use Siteworxpro\App\Async\Queues\Queue;
|
||||
use Siteworxpro\App\Services\Facades\Broker;
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Async\Handlers;
|
||||
|
||||
use Siteworxpro\App\Annotations\Async\HandlesMessage;
|
||||
use Siteworxpro\App\Attributes\Async\HandlesMessage;
|
||||
use Siteworxpro\App\Async\Messages\Message;
|
||||
use Siteworxpro\App\Async\Messages\SayHelloMessage;
|
||||
use Siteworxpro\App\Services\Facades\Logger;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Annotations\Async;
|
||||
namespace Siteworxpro\App\Attributes\Async;
|
||||
|
||||
use Attribute;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Annotations\Events;
|
||||
namespace Siteworxpro\App\Attributes\Events;
|
||||
|
||||
use Attribute;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Annotations\Guards;
|
||||
namespace Siteworxpro\App\Attributes\Guards;
|
||||
|
||||
use Attribute;
|
||||
use Siteworxpro\App\Services\Facades\Config;
|
||||
@@ -32,16 +32,6 @@ readonly class Jwt
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required audience from configuration, ignoring any local override.
|
||||
*
|
||||
* @return string The globally configured audience or an empty string if not set.
|
||||
*/
|
||||
public function getRequiredAudience(): string
|
||||
{
|
||||
return Config::get('jwt.audience') ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expected audience for validation.
|
||||
*
|
||||
12
src/Attributes/Guards/RequireAllScopes.php
Normal file
12
src/Attributes/Guards/RequireAllScopes.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Attributes\Guards;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
|
||||
readonly class RequireAllScopes
|
||||
{
|
||||
}
|
||||
38
src/Attributes/Guards/Scope.php
Normal file
38
src/Attributes/Guards/Scope.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Attributes\Guards;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||
readonly class Scope
|
||||
{
|
||||
/**
|
||||
* @param array<int, string> $scopes the required scopes
|
||||
* @param string $claim the claim to check for scopes
|
||||
* @param string $separator the separator used to split scopes in the claim
|
||||
*/
|
||||
public function __construct(
|
||||
private array $scopes = [],
|
||||
private string $claim = 'scope',
|
||||
private string $separator = ' '
|
||||
) {
|
||||
}
|
||||
|
||||
public function getScopes(): array
|
||||
{
|
||||
return $this->scopes;
|
||||
}
|
||||
|
||||
public function getClaim(): string
|
||||
{
|
||||
return $this->claim;
|
||||
}
|
||||
|
||||
public function getSeparator(): string
|
||||
{
|
||||
return $this->separator;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace Siteworxpro\App\Controllers;
|
||||
|
||||
use Nyholm\Psr7\ServerRequest;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Siteworxpro\App\Annotations\Guards;
|
||||
use Siteworxpro\App\Attributes\Guards;
|
||||
use Siteworxpro\App\Http\JsonResponseFactory;
|
||||
|
||||
/**
|
||||
@@ -22,7 +22,8 @@ class IndexController extends Controller
|
||||
* @throws \JsonException
|
||||
*/
|
||||
#[Guards\Jwt]
|
||||
#[Guards\Scope(['get.index'])]
|
||||
#[Guards\Scope(['get.index', 'status.check'])]
|
||||
#[Guards\RequireAllScopes]
|
||||
public function get(ServerRequest $request): ResponseInterface
|
||||
{
|
||||
return JsonResponseFactory::createJsonResponse(['status_code' => 200, 'message' => 'Server is running']);
|
||||
|
||||
@@ -7,7 +7,10 @@ namespace Siteworxpro\App\Events;
|
||||
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Siteworxpro\App\Annotations\Events\ListensFor;
|
||||
use Siteworxpro\App\Attributes\Events\ListensFor;
|
||||
|
||||
use function React\Async\await;
|
||||
use function React\Async\coroutine;
|
||||
|
||||
/**
|
||||
* Class Dispatcher
|
||||
@@ -29,6 +32,8 @@ class Dispatcher implements DispatcherContract, Arrayable
|
||||
*/
|
||||
private Collection $pushed;
|
||||
|
||||
private array $subscribers = [];
|
||||
|
||||
/**
|
||||
* @var string LISTENERS_NAMESPACE The namespace where listeners are located
|
||||
*/
|
||||
@@ -99,7 +104,7 @@ class Dispatcher implements DispatcherContract, Arrayable
|
||||
*/
|
||||
public function subscribe($subscriber): void
|
||||
{
|
||||
$this->listeners = array_merge($this->listeners, (array) $subscriber);
|
||||
$this->subscribers[] = $subscriber;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,6 +113,7 @@ class Dispatcher implements DispatcherContract, Arrayable
|
||||
* @param $event
|
||||
* @param array $payload
|
||||
* @return array|null
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function until($event, $payload = []): array|null
|
||||
{
|
||||
@@ -121,6 +127,7 @@ class Dispatcher implements DispatcherContract, Arrayable
|
||||
* @param array $payload
|
||||
* @param bool $halt
|
||||
* @return array|null
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function dispatch($event, $payload = [], $halt = false): array|null
|
||||
{
|
||||
@@ -130,23 +137,46 @@ class Dispatcher implements DispatcherContract, Arrayable
|
||||
$eventClass = $event;
|
||||
}
|
||||
|
||||
// Handle subscribers as a coroutine
|
||||
$promise = coroutine(function () use ($event, $payload, $halt, $eventClass, &$responses) {
|
||||
foreach ($this->subscribers as $subscriber) {
|
||||
if (method_exists($subscriber, 'handle')) {
|
||||
$response = $subscriber->handle($event, $payload);
|
||||
$responses[$eventClass] = $response;
|
||||
|
||||
if ($halt && $response !== null) {
|
||||
return $responses;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
$listeners = $this->listeners[$eventClass] ?? null;
|
||||
|
||||
// If no listeners, just await the subscriber promise
|
||||
if ($listeners === null) {
|
||||
return null;
|
||||
return await($promise);
|
||||
}
|
||||
|
||||
$responses = [];
|
||||
|
||||
foreach ($listeners as $listener) {
|
||||
$response = $listener($event, $payload);
|
||||
$responses[] = $response;
|
||||
$responses[$eventClass] = $response;
|
||||
|
||||
if ($halt && $response !== null) {
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
// Await the subscriber promise and merge responses
|
||||
$promiseResponses = await($promise);
|
||||
|
||||
if (is_array($promiseResponses)) {
|
||||
$responses = array_merge($responses, $promiseResponses);
|
||||
}
|
||||
|
||||
return $responses;
|
||||
}
|
||||
|
||||
@@ -167,6 +197,7 @@ class Dispatcher implements DispatcherContract, Arrayable
|
||||
*
|
||||
* @param $event
|
||||
* @return void
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function flush($event): void
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Siteworxpro\App\Events\Listeners\Database;
|
||||
|
||||
use Illuminate\Database\Events\ConnectionEstablished;
|
||||
use Illuminate\Database\Events\ConnectionEvent;
|
||||
use Siteworxpro\App\Annotations\Events\ListensFor;
|
||||
use Siteworxpro\App\Attributes\Events\ListensFor;
|
||||
use Siteworxpro\App\Events\Listeners\Listener;
|
||||
use Siteworxpro\App\Services\Facades\Logger;
|
||||
|
||||
@@ -18,12 +18,15 @@ use Siteworxpro\App\Services\Facades\Logger;
|
||||
class Connected extends Listener
|
||||
{
|
||||
/**
|
||||
* @param ConnectionEvent $event
|
||||
* @param mixed $event
|
||||
* @param array $payload
|
||||
* @return null
|
||||
*/
|
||||
public function __invoke($event, array $payload = []): null
|
||||
public function __invoke(mixed $event, array $payload = []): null
|
||||
{
|
||||
if (!($event instanceof ConnectionEvent)) {
|
||||
throw new \TypeError("Invalid event type passed to listener " . static::class);
|
||||
}
|
||||
|
||||
Logger::info("Database connection event", [get_class($event), $event->connectionName]);
|
||||
|
||||
|
||||
@@ -6,8 +6,11 @@ namespace Siteworxpro\App\Http\Middleware;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\WrapperClock;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Lcobucci\JWT\JwtFacade;
|
||||
use Lcobucci\JWT\Signer\Hmac\Sha256 as Hmac256;
|
||||
use Lcobucci\JWT\Signer\Key;
|
||||
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||
use Lcobucci\JWT\Signer\Rsa\Sha256;
|
||||
use Lcobucci\JWT\Token\InvalidTokenStructure;
|
||||
@@ -21,10 +24,11 @@ use League\Route\Dispatcher;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Siteworxpro\App\Annotations\Guards\Jwt;
|
||||
use Siteworxpro\App\Attributes\Guards\Jwt;
|
||||
use Siteworxpro\App\Controllers\Controller;
|
||||
use Siteworxpro\App\Http\JsonResponseFactory;
|
||||
use Siteworxpro\App\Services\Facades\Config;
|
||||
use Siteworxpro\App\Services\Facades\Redis;
|
||||
use Siteworxpro\HttpStatus\CodesEnum;
|
||||
|
||||
/**
|
||||
@@ -103,7 +107,7 @@ class JwtMiddleware extends Middleware
|
||||
/** @var Jwt $jwtInstance */
|
||||
$jwtInstance = $attribute->newInstance();
|
||||
|
||||
if ($jwtInstance->getRequiredAudience() !== '') {
|
||||
if ($jwtInstance->getAudience() !== '') {
|
||||
$requiredAudience = $jwtInstance->getAudience();
|
||||
}
|
||||
|
||||
@@ -114,7 +118,7 @@ class JwtMiddleware extends Middleware
|
||||
// Parse and validate the token with signature, time, issuer and audience constraints.
|
||||
$jwt = new JwtFacade()->parse(
|
||||
$token,
|
||||
$this->getSignedWith(),
|
||||
$this->getSignedWith($token),
|
||||
Config::get('jwt.strict_validation') ?
|
||||
new StrictValidAt(new WrapperClock(Carbon::now())) :
|
||||
new LooseValidAt(new WrapperClock(Carbon::now())),
|
||||
@@ -139,6 +143,11 @@ class JwtMiddleware extends Middleware
|
||||
'status_code' => 401,
|
||||
'message' => 'Unauthorized: Invalid token',
|
||||
], CodesEnum::UNAUTHORIZED);
|
||||
} catch (GuzzleException) {
|
||||
return JsonResponseFactory::createJsonResponse([
|
||||
'status_code' => 501,
|
||||
'message' => 'Token validation service unavailable',
|
||||
], CodesEnum::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// Expose all token claims as request attributes for downstream consumers.
|
||||
@@ -161,20 +170,31 @@ class JwtMiddleware extends Middleware
|
||||
* @return SignedWith Signature constraint used during JWT parsing.
|
||||
*
|
||||
* @throws \RuntimeException When no signing key is configured.
|
||||
* @throws GuzzleException On JWKS key retrieval issues.
|
||||
* @throws \JsonException
|
||||
*/
|
||||
private function getSignedWith(): SignedWith
|
||||
private function getSignedWith(string $token): SignedWith
|
||||
{
|
||||
$key = Config::get('jwt.signing_key');
|
||||
$keyConfig = Config::get('jwt.signing_key');
|
||||
|
||||
if ($key === null) {
|
||||
if ($keyConfig === null) {
|
||||
throw new \RuntimeException('JWT signing key is not configured.');
|
||||
}
|
||||
|
||||
// Load key either from file or raw text.
|
||||
if (str_starts_with($key, 'file://')) {
|
||||
$key = InMemory::file(substr($key, 7));
|
||||
// file:// path to key
|
||||
if (str_starts_with($keyConfig, 'file://')) {
|
||||
$key = InMemory::file(substr($keyConfig, 7));
|
||||
// openid jwks url
|
||||
} elseif (str_contains($keyConfig, '.well-known/')) {
|
||||
$jwt = explode('.', $token);
|
||||
if (count($jwt) !== 3) {
|
||||
throw new \RuntimeException('Invalid JWT structure for JWKS key retrieval.');
|
||||
}
|
||||
$header = json_decode(base64_decode($jwt[0]), true, 512, JSON_THROW_ON_ERROR);
|
||||
$keyId = $header['kid'] ?? '0'; // Default to '0' if no kid present
|
||||
$key = $this->getJwksKey($keyConfig, $keyId);
|
||||
} else {
|
||||
$key = InMemory::plainText($key);
|
||||
$key = InMemory::plainText($keyConfig);
|
||||
}
|
||||
|
||||
// Heuristic: if PEM public key content is detected, use RSA; otherwise use HMAC.
|
||||
@@ -184,4 +204,124 @@ class JwtMiddleware extends Middleware
|
||||
|
||||
return new SignedWith(new Hmac256(), $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
private function getJwksKey(string $url, string $keyId): Key
|
||||
{
|
||||
$cached = Redis::get('jwks_key_' . $keyId);
|
||||
if ($cached !== null) {
|
||||
return InMemory::plainText($cached);
|
||||
}
|
||||
|
||||
$client = new Client();
|
||||
$openIdConfig = $client->get($url);
|
||||
$body = json_decode($openIdConfig->getBody()->getContents(), true, JSON_THROW_ON_ERROR);
|
||||
$jwksUri = $body['jwks_uri'] ?? '';
|
||||
if (empty($jwksUri)) {
|
||||
throw new \RuntimeException('JWKS URI not found in OpenID configuration.');
|
||||
}
|
||||
|
||||
$jwksResponse = $client->get($jwksUri);
|
||||
$jwksBody = json_decode(
|
||||
$jwksResponse->getBody()->getContents(),
|
||||
true,
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
|
||||
// For simplicity, we take the first key in the JWKS.
|
||||
$firstKey = array_filter(
|
||||
$jwksBody['keys'],
|
||||
fn($key) => $key['kid'] === $keyId
|
||||
)[0] ?? null;
|
||||
|
||||
if (empty($firstKey)) {
|
||||
throw new \RuntimeException('No matching key found in JWKS for key ID: ' . $keyId);
|
||||
}
|
||||
|
||||
$n = $firstKey['n'];
|
||||
$e = $firstKey['e'];
|
||||
$publicKeyPem = "-----BEGIN PUBLIC KEY-----\n" .
|
||||
chunk_split(base64_encode($this->convertJwkToPem($n, $e)), 64) .
|
||||
"-----END PUBLIC KEY-----\n";
|
||||
|
||||
Redis::set('jwks_key_' . $keyId, $publicKeyPem, 'EX', 3600);
|
||||
|
||||
return InMemory::plainText($publicKeyPem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a DER-encoded SubjectPublicKeyInfo from JWK 'n' and 'e'.
|
||||
* Returns raw DER bytes; caller base64-encodes and wraps with PEM headers.
|
||||
*/
|
||||
private function convertJwkToPem(string $n, string $e): string
|
||||
{
|
||||
$modulus = $this->base64UrlDecode($n);
|
||||
$exponent = $this->base64UrlDecode($e);
|
||||
|
||||
$derN = $this->derEncodeInteger($modulus);
|
||||
$derE = $this->derEncodeInteger($exponent);
|
||||
|
||||
// RSAPublicKey (PKCS#1): SEQUENCE { n INTEGER, e INTEGER }
|
||||
$rsaPublicKey = $this->derEncodeSequence($derN . $derE);
|
||||
|
||||
// AlgorithmIdentifier for rsaEncryption: 1.2.840.113549.1.1.1 with NULL
|
||||
$algId = hex2bin('300d06092a864886f70d0101010500');
|
||||
|
||||
// SubjectPublicKey (SPKI) BIT STRING, 0 unused bits + RSAPublicKey
|
||||
$subjectPublicKey = $this->derEncodeBitString($rsaPublicKey);
|
||||
|
||||
// SubjectPublicKeyInfo: SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING }
|
||||
return $this->derEncodeSequence($algId . $subjectPublicKey);
|
||||
}
|
||||
|
||||
private function base64UrlDecode(string $data): string
|
||||
{
|
||||
$data = strtr($data, '-_', '+/');
|
||||
$pad = strlen($data) % 4;
|
||||
if ($pad) {
|
||||
$data .= str_repeat('=', 4 - $pad);
|
||||
}
|
||||
return base64_decode($data);
|
||||
}
|
||||
|
||||
private function derEncodeLength(int $len): string
|
||||
{
|
||||
if ($len < 0x80) {
|
||||
return chr($len);
|
||||
}
|
||||
$bytes = '';
|
||||
while ($len > 0) {
|
||||
$bytes = chr($len & 0xFF) . $bytes;
|
||||
$len >>= 8;
|
||||
}
|
||||
return chr(0x80 | strlen($bytes)) . $bytes;
|
||||
}
|
||||
|
||||
private function derEncodeInteger(string $bytes): string
|
||||
{
|
||||
// Remove leading zeroes
|
||||
$bytes = ltrim($bytes, "\x00");
|
||||
if ($bytes === '') {
|
||||
$bytes = "\x00";
|
||||
}
|
||||
// Ensure positive INTEGER (prepend 0x00 if MSB set)
|
||||
if ((ord($bytes[0]) & 0x80) !== 0) {
|
||||
$bytes = "\x00" . $bytes;
|
||||
}
|
||||
return "\x02" . $this->derEncodeLength(strlen($bytes)) . $bytes;
|
||||
}
|
||||
|
||||
private function derEncodeSequence(string $bytes): string
|
||||
{
|
||||
return "\x30" . $this->derEncodeLength(strlen($bytes)) . $bytes;
|
||||
}
|
||||
|
||||
private function derEncodeBitString(string $bytes): string
|
||||
{
|
||||
// 0 unused bits + data
|
||||
$payload = "\x00" . $bytes;
|
||||
return "\x03" . $this->derEncodeLength(strlen($payload)) . $payload;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ use League\Route\Dispatcher;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Siteworxpro\App\Annotations\Guards\Scope;
|
||||
use Siteworxpro\App\Attributes\Guards\RequireAllScopes;
|
||||
use Siteworxpro\App\Attributes\Guards\Scope;
|
||||
use Siteworxpro\App\Controllers\Controller;
|
||||
use Siteworxpro\App\Http\JsonResponseFactory;
|
||||
use Siteworxpro\HttpStatus\CodesEnum;
|
||||
@@ -42,7 +43,7 @@ class ScopeMiddleware extends Middleware
|
||||
*/
|
||||
public function process(
|
||||
ServerRequestInterface $request,
|
||||
RequestHandlerInterface | Dispatcher $handler
|
||||
RequestHandlerInterface|Dispatcher $handler
|
||||
): ResponseInterface {
|
||||
// Attempt to resolve the route's callable [Controller instance, method name].
|
||||
$callable = $this->extractRouteCallable($handler);
|
||||
@@ -57,26 +58,49 @@ class ScopeMiddleware extends Middleware
|
||||
// Ensure the controller exists and the method is defined before reflecting.
|
||||
if (class_exists($class::class)) {
|
||||
$reflectionClass = new \ReflectionClass($class);
|
||||
|
||||
if ($reflectionClass->hasMethod($method)) {
|
||||
$reflectionMethod = $reflectionClass->getMethod($method);
|
||||
|
||||
// Fetch all Scope attributes declared on the method.
|
||||
$attributes = $reflectionMethod->getAttributes(Scope::class);
|
||||
$requireAllAttributes = $reflectionMethod->getAttributes(RequireAllScopes::class);
|
||||
|
||||
if (empty($attributes)) {
|
||||
// No scope attributes; delegate to the next handler.
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
$requiredScopes = [];
|
||||
$userScopes = [];
|
||||
$requireAll = false;
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
/** @var Scope $scopeInstance Concrete Scope attribute instance. */
|
||||
$scopeInstance = $attribute->newInstance();
|
||||
$requiredScopes = $scopeInstance->getScopes();
|
||||
$requiredScopes = array_merge($requiredScopes, $scopeInstance->getScopes());
|
||||
|
||||
// Retrieve user scopes from the request (defaults to an empty array).
|
||||
$userScopes = $request->getAttribute('scopes', []);
|
||||
// If any attribute requires all scopes, set the flag.
|
||||
$requireAll = $requireAll || !empty($requireAllAttributes);
|
||||
|
||||
$scopes = $request->getAttribute($scopeInstance->getClaim());
|
||||
if (!is_array($scopes)) {
|
||||
// If user scopes are not an array, treat as no scopes provided.
|
||||
$scopes = explode($scopeInstance->getSeparator(), (string) $scopes);
|
||||
}
|
||||
|
||||
$userScopes = array_merge(
|
||||
$userScopes,
|
||||
$scopes
|
||||
);
|
||||
}
|
||||
|
||||
$userScopes = array_unique($userScopes);
|
||||
|
||||
// Deny if any required scope is missing from the user's scopes.
|
||||
if (
|
||||
array_any(
|
||||
$requiredScopes,
|
||||
fn($requiredScope) => !in_array($requiredScope, $userScopes, true)
|
||||
)
|
||||
(!$requireAll && array_intersect($userScopes, $requiredScopes) === []) ||
|
||||
($requireAll && array_diff($requiredScopes, $userScopes) !== [])
|
||||
) {
|
||||
return JsonResponseFactory::createJsonResponse([
|
||||
'error' => 'insufficient_scope',
|
||||
@@ -86,7 +110,6 @@ class ScopeMiddleware extends Middleware
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All checks passed; continue down the middleware pipeline.
|
||||
return $handler->handle($request);
|
||||
|
||||
@@ -14,6 +14,7 @@ use Siteworxpro\App\Services\Facade;
|
||||
* It extends the Facade class from the Illuminate\Support\Facades namespace.
|
||||
*
|
||||
* @method static array | bool | string | int | null get(string $key) Retrieve the configuration value for the given key. // @codingStandardsIgnoreStart
|
||||
* @method static void set(string $key, mixed $value) Set the configuration value for the given key. // @codingStandardsIgnoreEnd
|
||||
*
|
||||
* @package Siteworx\App\Facades
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Siteworxpro\App\Services\ServiceProviders;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Siteworxpro\App\Log\Logger;
|
||||
use Siteworxpro\App\Services\Facades\Config;
|
||||
|
||||
/**
|
||||
* Class LoggerServiceProvider
|
||||
@@ -17,7 +18,7 @@ class LoggerServiceProvider extends ServiceProvider
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton(Logger::class, function () {
|
||||
return new Logger();
|
||||
return new Logger(Config::get('app.log_level'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
49
tests/Attributes/Guards/JwtTest.php
Normal file
49
tests/Attributes/Guards/JwtTest.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\Tests\Attributes\Guards;
|
||||
|
||||
use Siteworxpro\App\Attributes\Guards\Jwt;
|
||||
use Siteworxpro\App\Services\Facades\Config;
|
||||
use Siteworxpro\Tests\Unit;
|
||||
|
||||
class JwtTest extends Unit
|
||||
{
|
||||
public function testGetsClassFromConfig(): void
|
||||
{
|
||||
Config::set('jwt.issuer', 'default-issuer');
|
||||
Config::set('jwt.audience', 'default-audience');
|
||||
|
||||
$reflection = new \ReflectionClass(TestClass::class);
|
||||
$attributes = $reflection->getAttributes(Jwt::class);
|
||||
$this->assertCount(1, $attributes);
|
||||
|
||||
/** @var Jwt $instance */
|
||||
$instance = $attributes[0]->newInstance();
|
||||
$this->assertEquals('default-audience', $instance->getAudience());
|
||||
$this->assertEquals('default-issuer', $instance->getIssuer());
|
||||
}
|
||||
|
||||
public function testGetsClassFromCustom(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(TestClassSpecific::class);
|
||||
$attributes = $reflection->getAttributes(Jwt::class);
|
||||
$this->assertCount(1, $attributes);
|
||||
|
||||
/** @var Jwt $instance */
|
||||
$instance = $attributes[0]->newInstance();
|
||||
$this->assertEquals('custom-audience', $instance->getAudience());
|
||||
$this->assertEquals('custom-issuer', $instance->getIssuer());
|
||||
}
|
||||
}
|
||||
|
||||
#[Jwt]
|
||||
class TestClass // @codingStandardsIgnoreLine
|
||||
{
|
||||
}
|
||||
|
||||
#[Jwt('custom-issuer', 'custom-audience')]
|
||||
class TestClassSpecific // @codingStandardsIgnoreLine
|
||||
{
|
||||
}
|
||||
43
tests/Attributes/Guards/ScopeTest.php
Normal file
43
tests/Attributes/Guards/ScopeTest.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\Tests\Attributes\Guards;
|
||||
|
||||
use Siteworxpro\App\Attributes\Guards\Scope;
|
||||
use Siteworxpro\Tests\Unit;
|
||||
|
||||
class ScopeTest extends Unit
|
||||
{
|
||||
public function testGetsClassSingle(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(TestClassSingle::class);
|
||||
$attributes = $reflection->getAttributes(Scope::class);
|
||||
$this->assertCount(1, $attributes);
|
||||
|
||||
/** @var Scope $instance */
|
||||
$instance = $attributes[0]->newInstance();
|
||||
$this->assertEquals(['read:users'], $instance->getScopes());
|
||||
}
|
||||
|
||||
public function testGetsClassFromCustom(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(TestClassMultiple::class);
|
||||
$attributes = $reflection->getAttributes(Scope::class);
|
||||
$this->assertCount(1, $attributes);
|
||||
|
||||
/** @var Scope $instance */
|
||||
$instance = $attributes[0]->newInstance();
|
||||
$this->assertEquals(['read:users', 'write:users'], $instance->getScopes());
|
||||
}
|
||||
}
|
||||
|
||||
#[Scope(['read:users', 'write:users'])]
|
||||
class TestClassMultiple // @codingStandardsIgnoreLine
|
||||
{
|
||||
}
|
||||
|
||||
#[Scope(['read:users'])]
|
||||
class TestClassSingle // @codingStandardsIgnoreLine
|
||||
{
|
||||
}
|
||||
25
tests/Attributes/HandlesMessageTest.php
Normal file
25
tests/Attributes/HandlesMessageTest.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\Tests\Attributes;
|
||||
|
||||
use Siteworxpro\App\Attributes\Async\HandlesMessage;
|
||||
use Siteworxpro\Tests\Unit;
|
||||
|
||||
class HandlesMessageTest extends Unit
|
||||
{
|
||||
public function testGetsClass(): void
|
||||
{
|
||||
$class = new #[HandlesMessage('Siteworxpro\Tests\Attributes\TestClass')] class {
|
||||
};
|
||||
|
||||
$reflection = new \ReflectionClass($class);
|
||||
$attributes = $reflection->getAttributes(HandlesMessage::class);
|
||||
$this->assertCount(1, $attributes);
|
||||
|
||||
/** @var HandlesMessage $instance */
|
||||
$instance = $attributes[0]->newInstance();
|
||||
$this->assertEquals('Siteworxpro\Tests\Attributes\TestClass', $instance->getMessageClass());
|
||||
}
|
||||
}
|
||||
@@ -22,4 +22,19 @@ class IndexControllerTest extends AbstractController
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals('{"status_code":200,"message":"Server is running"}', (string)$response->getBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function testPost(): void
|
||||
{
|
||||
$this->assertTrue(true);
|
||||
|
||||
$controller = new IndexController();
|
||||
|
||||
$response = $controller->post($this->getMockRequest());
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals('{"status_code":200,"message":"Server is running"}', (string)$response->getBody());
|
||||
}
|
||||
}
|
||||
|
||||
47
tests/Events/Listeners/ConnectedTest.php
Normal file
47
tests/Events/Listeners/ConnectedTest.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\Tests\Events\Listeners;
|
||||
|
||||
use Illuminate\Database\Events\ConnectionEstablished;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use Siteworxpro\App\Events\Listeners\Database\Connected;
|
||||
use Siteworxpro\App\Log\Logger;
|
||||
use Siteworxpro\Tests\Unit;
|
||||
|
||||
class ConnectedTest extends Unit
|
||||
{
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws \ReflectionException
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$inputBuffer = fopen('php://memory', 'r+');
|
||||
$logger = new Logger(LogLevel::DEBUG, $inputBuffer);
|
||||
\Siteworxpro\App\Services\Facades\Logger::getFacadeContainer()->bind(Logger::class, fn() => $logger);
|
||||
}
|
||||
|
||||
public function testHandlesEvent()
|
||||
{
|
||||
$this->expectNotToPerformAssertions();
|
||||
|
||||
$connectedEvent = $this->createMock(ConnectionEstablished::class);
|
||||
$listener = new Connected();
|
||||
|
||||
$listener->__invoke($connectedEvent);
|
||||
}
|
||||
|
||||
public function testThrowsException()
|
||||
{
|
||||
$this->expectException(\TypeError::class);
|
||||
$listener = new Connected();
|
||||
$listener->__invoke(new \stdClass());
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ use Siteworxpro\App\Http\Middleware\CorsMiddleware;
|
||||
use Siteworxpro\App\Services\Facades\Config;
|
||||
use Siteworxpro\Tests\Unit;
|
||||
|
||||
class CorsMiddlewareTest extends Unit
|
||||
class CorsMiddlewareTest extends Middleware
|
||||
{
|
||||
public function testAllowsConfiguredOrigin(): void
|
||||
{
|
||||
@@ -80,22 +80,4 @@ class CorsMiddlewareTest extends Unit
|
||||
|
||||
$this->assertEquals('true', $response->getHeaderLine('Access-Control-Allow-Credentials'));
|
||||
}
|
||||
|
||||
private function mockHandler(Response $response): RequestHandlerInterface
|
||||
{
|
||||
return new class ($response) implements RequestHandlerInterface {
|
||||
private Response $response;
|
||||
|
||||
public function __construct(Response $response)
|
||||
{
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
public function handle(
|
||||
\Psr\Http\Message\ServerRequestInterface $request
|
||||
): \Psr\Http\Message\ResponseInterface {
|
||||
return $this->response;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
230
tests/Http/Middleware/JwtMiddlewareTest.php
Normal file
230
tests/Http/Middleware/JwtMiddlewareTest.php
Normal file
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\Tests\Http\Middleware;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use Lcobucci\JWT\JwtFacade;
|
||||
use Lcobucci\JWT\Signer\Hmac\Sha256;
|
||||
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||
use Lcobucci\JWT\Token\Builder;
|
||||
use League\Route\Dispatcher;
|
||||
use Nyholm\Psr7\Response;
|
||||
use Nyholm\Psr7\ServerRequest;
|
||||
use Siteworxpro\App\Attributes\Guards\Jwt;
|
||||
use Siteworxpro\App\Http\Middleware\JwtMiddleware;
|
||||
use Siteworxpro\App\Services\Facades\Config;
|
||||
use Siteworxpro\HttpStatus\CodesEnum;
|
||||
|
||||
class JwtMiddlewareTest extends Middleware
|
||||
{
|
||||
private const string TEST_SIGNING_KEY = 'test_signing_key_123456444478901234';
|
||||
|
||||
public function getClass(): object
|
||||
{
|
||||
return new class {
|
||||
public function getCallable(): array
|
||||
{
|
||||
return [$this, 'index'];
|
||||
}
|
||||
|
||||
#[Jwt]
|
||||
public function index()
|
||||
{
|
||||
// Dummy method for testing
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Config::set('jwt.signing_key', self::TEST_SIGNING_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function testIgnoresNoJwtAttribute()
|
||||
{
|
||||
$class = new class {
|
||||
public function getCallable(): array
|
||||
{
|
||||
return [ $this, 'index' ];
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
// Dummy method for testing
|
||||
}
|
||||
};
|
||||
|
||||
$handler = \Mockery::mock(Dispatcher::class);
|
||||
$handler->shouldReceive('getMiddlewareStack')
|
||||
->andReturn([$class]);
|
||||
|
||||
$handler
|
||||
->shouldReceive('handle')
|
||||
->once()
|
||||
->andReturn(new Response(200));
|
||||
|
||||
$request = new ServerRequest('GET', '/');
|
||||
$middleware = new JwtMiddleware();
|
||||
$response = $middleware->process($request, $handler);
|
||||
$this->assertEquals(CodesEnum::OK->value, $response->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function testIgnoresJwtAttributeButNoToken()
|
||||
{
|
||||
$class = $this->getClass();
|
||||
|
||||
$handler = \Mockery::mock(Dispatcher::class);
|
||||
$handler->shouldReceive('getMiddlewareStack')
|
||||
->andReturn([$class]);
|
||||
|
||||
$request = new ServerRequest('GET', '/');
|
||||
$middleware = new JwtMiddleware();
|
||||
$response = $middleware->process($request, $handler);
|
||||
$this->assertEquals(CodesEnum::UNAUTHORIZED->value, $response->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function testInvalidToken()
|
||||
{
|
||||
$class = $this->getClass();
|
||||
|
||||
$handler = \Mockery::mock(Dispatcher::class);
|
||||
$handler->shouldReceive('getMiddlewareStack')
|
||||
->andReturn([$class]);
|
||||
|
||||
$request = new ServerRequest('GET', '/');
|
||||
$request = $request->withHeader('Authorization', 'Bearer ' . 'invalid_token_string');
|
||||
$middleware = new JwtMiddleware();
|
||||
$response = $middleware->process($request, $handler);
|
||||
$this->assertEquals(CodesEnum::UNAUTHORIZED->value, $response->getStatusCode());
|
||||
$this->assertStringContainsString(
|
||||
'Unauthorized: Invalid token',
|
||||
$response->getBody()->getContents()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function testJwtAttributeWithTokenButWrongAud()
|
||||
{
|
||||
$class = $this->getClass();
|
||||
|
||||
$handler = \Mockery::mock(Dispatcher::class);
|
||||
$handler->shouldReceive('getMiddlewareStack')
|
||||
->andReturn([$class]);
|
||||
|
||||
$request = new ServerRequest('GET', '/');
|
||||
$request = $request->withHeader('Authorization', 'Bearer ' . $this->getJwt());
|
||||
$middleware = new JwtMiddleware();
|
||||
$response = $middleware->process($request, $handler);
|
||||
$this->assertEquals(CodesEnum::UNAUTHORIZED->value, $response->getStatusCode());
|
||||
$this->assertStringContainsString(
|
||||
'The token is not allowed to be used by this audience',
|
||||
$response->getBody()->getContents()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function testJwtAttributeWithTokenButWrongIss()
|
||||
{
|
||||
Config::set('jwt.audience', 'https://client-app.io');
|
||||
|
||||
$class = $this->getClass();
|
||||
|
||||
$handler = \Mockery::mock(Dispatcher::class);
|
||||
$handler->shouldReceive('getMiddlewareStack')
|
||||
->andReturn([$class]);
|
||||
|
||||
$request = new ServerRequest('GET', '/');
|
||||
$request = $request->withHeader('Authorization', 'Bearer ' . $this->getJwt());
|
||||
$middleware = new JwtMiddleware();
|
||||
$response = $middleware->process($request, $handler);
|
||||
$this->assertEquals(CodesEnum::UNAUTHORIZED->value, $response->getStatusCode());
|
||||
$this->assertStringContainsString(
|
||||
'The token was not issued by the given issuers',
|
||||
$response->getBody()->getContents()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function testJwtAttributeWithTokenWithDiffIssuer()
|
||||
{
|
||||
Config::set('jwt.audience', 'https://client-app.io');
|
||||
Config::set('jwt.issuer', 'https://different-issuer.io');
|
||||
|
||||
$class = $this->getClass();
|
||||
|
||||
$handler = \Mockery::mock(Dispatcher::class);
|
||||
$handler->shouldReceive('getMiddlewareStack')
|
||||
->andReturn([$class]);
|
||||
|
||||
$request = new ServerRequest('GET', '/');
|
||||
$request = $request->withHeader('Authorization', 'Bearer ' . $this->getJwt());
|
||||
$middleware = new JwtMiddleware();
|
||||
$response = $middleware->process($request, $handler);
|
||||
$this->assertEquals(CodesEnum::UNAUTHORIZED->value, $response->getStatusCode());
|
||||
$this->assertStringContainsString(
|
||||
'The token was not issued by the given issuers',
|
||||
$response->getBody()->getContents()
|
||||
);
|
||||
}
|
||||
|
||||
public function testJwtAttributeWithToken()
|
||||
{
|
||||
Config::set('jwt.audience', 'https://client-app.io');
|
||||
Config::set('jwt.issuer', 'https://api.my-awesome-app.io');
|
||||
|
||||
$class = $this->getClass();
|
||||
|
||||
$handler = \Mockery::mock(Dispatcher::class);
|
||||
$handler->shouldReceive('getMiddlewareStack')
|
||||
->andReturn([$class]);
|
||||
|
||||
$handler
|
||||
->shouldReceive('handle')
|
||||
->once()
|
||||
->andReturn(new Response(200));
|
||||
|
||||
$request = new ServerRequest('GET', '/');
|
||||
$request = $request->withHeader('Authorization', 'Bearer ' . $this->getJwt());
|
||||
$middleware = new JwtMiddleware();
|
||||
$response = $middleware->process($request, $handler);
|
||||
$this->assertEquals(CodesEnum::OK->value, $response->getStatusCode());
|
||||
}
|
||||
|
||||
private function getJwt(): string
|
||||
{
|
||||
$key = InMemory::plainText(self::TEST_SIGNING_KEY);
|
||||
$signer = new Sha256();
|
||||
|
||||
$token = new JwtFacade()->issue(
|
||||
$signer,
|
||||
$key,
|
||||
static fn (
|
||||
Builder $builder,
|
||||
DateTimeImmutable $issuedAt
|
||||
): Builder => $builder
|
||||
->issuedBy('https://api.my-awesome-app.io')
|
||||
->permittedFor('https://client-app.io')
|
||||
->expiresAt($issuedAt->modify('+10 minutes'))
|
||||
);
|
||||
|
||||
return $token->toString();
|
||||
}
|
||||
}
|
||||
32
tests/Http/Middleware/Middleware.php
Normal file
32
tests/Http/Middleware/Middleware.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\Tests\Http\Middleware;
|
||||
|
||||
use Nyholm\Psr7\Response;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Siteworxpro\Tests\Unit;
|
||||
|
||||
abstract class Middleware extends Unit
|
||||
{
|
||||
protected function mockHandler(Response $response): RequestHandlerInterface
|
||||
{
|
||||
return new class ($response) implements RequestHandlerInterface {
|
||||
private Response $response;
|
||||
|
||||
public function __construct(Response $response)
|
||||
{
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
public function handle(
|
||||
ServerRequestInterface $request
|
||||
): ResponseInterface {
|
||||
return $this->response;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
111
tests/Http/Middleware/ScopeMiddlewareTest.php
Normal file
111
tests/Http/Middleware/ScopeMiddlewareTest.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\Tests\Http\Middleware;
|
||||
|
||||
use League\Route\Dispatcher;
|
||||
use Nyholm\Psr7\Response;
|
||||
use Nyholm\Psr7\ServerRequest;
|
||||
use Siteworxpro\App\Attributes\Guards\Scope;
|
||||
use Siteworxpro\App\Http\Middleware\ScopeMiddleware;
|
||||
use Siteworxpro\HttpStatus\CodesEnum;
|
||||
|
||||
class ScopeMiddlewareTest extends Middleware
|
||||
{
|
||||
/**
|
||||
* @throws \ReflectionException
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function testHandlesNoScopes()
|
||||
{
|
||||
$class = new class {
|
||||
public function getCallable(): array
|
||||
{
|
||||
return [ $this, 'index' ];
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
// Dummy method for testing
|
||||
}
|
||||
};
|
||||
|
||||
$handler = \Mockery::mock(Dispatcher::class);
|
||||
$handler->shouldReceive('getMiddlewareStack')
|
||||
->andReturn([$class]);
|
||||
|
||||
$handler
|
||||
->shouldReceive('handle')
|
||||
->once()
|
||||
->andReturn(new Response(200));
|
||||
|
||||
$request = new ServerRequest('GET', '/');
|
||||
$middleware = new ScopeMiddleware();
|
||||
$response = $middleware->process($request, $handler);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ReflectionException
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function testAllowsWithScope()
|
||||
{
|
||||
$class = new class {
|
||||
public function getCallable(): array
|
||||
{
|
||||
return [ $this, 'index' ];
|
||||
}
|
||||
|
||||
#[Scope(['admin'])]
|
||||
public function index()
|
||||
{
|
||||
// Dummy method for testing
|
||||
}
|
||||
};
|
||||
|
||||
$handler = \Mockery::mock(Dispatcher::class);
|
||||
$handler->shouldReceive('getMiddlewareStack')
|
||||
->andReturn([$class]);
|
||||
|
||||
$handler
|
||||
->shouldReceive('handle')
|
||||
->once()
|
||||
->andReturn(new Response(200));
|
||||
|
||||
$request = new ServerRequest('GET', '/')->withAttribute('scope', ['admin', 'user']);
|
||||
$middleware = new ScopeMiddleware();
|
||||
$response = $middleware->process($request, $handler);
|
||||
$this->assertEquals(CodesEnum::OK->value, $response->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ReflectionException
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function testDisallowsWithScope()
|
||||
{
|
||||
$class = new class {
|
||||
public function getCallable(): array
|
||||
{
|
||||
return [ $this, 'index' ];
|
||||
}
|
||||
|
||||
#[Scope(['admin'])]
|
||||
public function index()
|
||||
{
|
||||
// Dummy method for testing
|
||||
}
|
||||
};
|
||||
|
||||
$handler = \Mockery::mock(Dispatcher::class);
|
||||
$handler->shouldReceive('getMiddlewareStack')
|
||||
->andReturn([$class]);
|
||||
|
||||
$request = new ServerRequest('GET', '/');
|
||||
$middleware = new ScopeMiddleware();
|
||||
$response = $middleware->process($request, $handler);
|
||||
$this->assertEquals(CodesEnum::FORBIDDEN->value, $response->getStatusCode());
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ class LoggerRpcTest extends Unit
|
||||
$mock = Mockery::mock(LoggerInterface::class);
|
||||
$mock->expects('debug')
|
||||
->with('message', ['key' => 'value'])
|
||||
->once();
|
||||
->times(1);
|
||||
|
||||
\Siteworxpro\App\Services\Facades\Logger::getFacadeContainer()
|
||||
->bind(\RoadRunner\Logger\Logger::class, function () use ($mock) {
|
||||
@@ -46,8 +46,6 @@ class LoggerRpcTest extends Unit
|
||||
$logger->debug('message', ['key' => 'value']);
|
||||
|
||||
$mock->shouldHaveReceived('debug');
|
||||
|
||||
Mockery::close();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,7 +74,6 @@ class LoggerRpcTest extends Unit
|
||||
$logger->notice('message', ['key' => 'value']);
|
||||
|
||||
$mock->shouldHaveReceived('info')->times(2);
|
||||
Mockery::close();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,7 +101,6 @@ class LoggerRpcTest extends Unit
|
||||
$logger->warning('message', ['key' => 'value']);
|
||||
|
||||
$mock->shouldHaveReceived('warning');
|
||||
Mockery::close();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,7 +131,6 @@ class LoggerRpcTest extends Unit
|
||||
$logger->emergency('message', ['key' => 'value']);
|
||||
|
||||
$mock->shouldHaveReceived('error')->times(4);
|
||||
Mockery::close();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,6 +157,5 @@ class LoggerRpcTest extends Unit
|
||||
$logger->log('notaloglevel', 'message', ['key' => 'value']);
|
||||
|
||||
$mock->shouldHaveReceived('log')->times(1);
|
||||
Mockery::close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\Tests\Log;
|
||||
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use Siteworxpro\App\Log\Logger;
|
||||
use Siteworxpro\Tests\Unit;
|
||||
|
||||
class LoggerTest extends Unit
|
||||
{
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
private function getLoggerWithBuffer(string $logLevel): array
|
||||
{
|
||||
$inputBuffer = fopen('php://memory', 'r+');
|
||||
@@ -21,6 +27,10 @@ class LoggerTest extends Unit
|
||||
return stream_get_contents($inputBuffer, -1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
private function testLogLevel(string $level): void
|
||||
{
|
||||
[$logger, $inputBuffer] = $this->getLoggerWithBuffer($level);
|
||||
@@ -33,6 +43,10 @@ class LoggerTest extends Unit
|
||||
$this->assertEquals('value', $decoded['context']['key']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
private function testLogLevelEmpty(string $configLevel, string $logLevel): void
|
||||
{
|
||||
[$logger, $inputBuffer] = $this->getLoggerWithBuffer($configLevel);
|
||||
@@ -42,57 +56,101 @@ class LoggerTest extends Unit
|
||||
$this->assertEmpty($output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function testLogsDebugMessageWhenLevelIsDebug(): void
|
||||
{
|
||||
$this->testLogLevel(LogLevel::DEBUG);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function testLogsInfoMessageWhenLevelIsInfo(): void
|
||||
{
|
||||
$this->testLogLevel(LogLevel::INFO);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function testLogsWarningMessageWhenLevelIsWarning(): void
|
||||
{
|
||||
$this->testLogLevel(LogLevel::WARNING);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function testLogsErrorMessageWhenLevelIsError(): void
|
||||
{
|
||||
$this->testLogLevel(LogLevel::ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function testLogsCriticalMessageWhenLevelIsCritical(): void
|
||||
{
|
||||
$this->testLogLevel(LogLevel::CRITICAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function testLogsAlertMessageWhenLevelIsAlert(): void
|
||||
{
|
||||
$this->testLogLevel(LogLevel::ALERT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function testLogsEmergencyMessageWhenLevelIsEmergency(): void
|
||||
{
|
||||
$this->testLogLevel(LogLevel::EMERGENCY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function testLogsNoticeMessageWhenLevelIsNotice(): void
|
||||
{
|
||||
$this->testLogLevel(LogLevel::NOTICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function testDoesNotLogWhenMinimumLevelIsInfo(): void
|
||||
{
|
||||
$this->testLogLevelEmpty(LogLevel::INFO, LogLevel::DEBUG);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function testDoesNotLogWhenMinimumLevelIsWarning(): void
|
||||
{
|
||||
$this->testLogLevelEmpty(LogLevel::WARNING, LogLevel::INFO);
|
||||
$this->testLogLevelEmpty(LogLevel::WARNING, LogLevel::DEBUG);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws ContainerExceptionInterface
|
||||
*/
|
||||
public function testDoesNotLogWhenMinimumLevelIsError(): void
|
||||
{
|
||||
$this->testLogLevelEmpty(LogLevel::ERROR, LogLevel::DEBUG);
|
||||
@@ -100,12 +158,20 @@ class LoggerTest extends Unit
|
||||
$this->testLogLevelEmpty(LogLevel::ERROR, LogLevel::WARNING);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function testDoesNotLogWhenMinimumLevelIsNotice(): void
|
||||
{
|
||||
$this->testLogLevelEmpty(LogLevel::NOTICE, LogLevel::DEBUG);
|
||||
$this->testLogLevelEmpty(LogLevel::NOTICE, LogLevel::INFO);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function testLogsMessageWithEmptyContext(): void
|
||||
{
|
||||
[$logger, $buffer] = $this->getLoggerWithBuffer(LogLevel::INFO);
|
||||
@@ -118,6 +184,10 @@ class LoggerTest extends Unit
|
||||
$this->assertEquals('Message without context', $decoded['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function testLogsMessageWithComplexContext(): void
|
||||
{
|
||||
[$logger, $buffer] = $this->getLoggerWithBuffer(LogLevel::INFO);
|
||||
@@ -135,6 +205,10 @@ class LoggerTest extends Unit
|
||||
$this->assertEquals('value', $decoded['context']['nested']['key']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function testLogsStringableMessage(): void
|
||||
{
|
||||
[$logger, $buffer] = $this->getLoggerWithBuffer(LogLevel::INFO);
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Siteworxpro\Tests;
|
||||
|
||||
use Illuminate\Container\Container;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Siteworx\Config\Config as SWConfig;
|
||||
use Siteworxpro\App\Services\Facade;
|
||||
@@ -29,5 +30,6 @@ abstract class Unit extends TestCase
|
||||
{
|
||||
Config::clearResolvedInstances();
|
||||
Facade::setFacadeContainer(null);
|
||||
Mockery::close();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user