This commit is contained in:
2025-04-25 22:27:47 -04:00
parent ac99a78bdf
commit d5167074a0
17 changed files with 173 additions and 30 deletions

View File

@@ -1,2 +1,3 @@
.idea/
vendor/
vendor/
.phpunit.cache/

View File

@@ -9,9 +9,15 @@ Unit Tests:
when: always
- when: never
image: siteworxpro/composer
before_script: |
bin/pcov.sh
script:
- echo "Running unit tests..."
- composer run tests:unit
artifacts:
expire_in: 1 day
paths:
- tests/reports/
Run License Check:
stage: tests
@@ -42,15 +48,15 @@ Run Code Lint:
- composer run tests:lint
Run Code Sniffer:
stage: tests
needs:
- Install Composer Libraries
rules:
- if: '$CI_COMMIT_TAG'
when: never
- if: '$CI_PIPELINE_SOURCE == "push"'
when: on_success
- when: never
image: siteworxpro/composer
script:
- composer run tests:phpstan
stage: tests
needs:
- Install Composer Libraries
rules:
- if: '$CI_COMMIT_TAG'
when: never
- if: '$CI_PIPELINE_SOURCE == "push"'
when: on_success
- when: never
image: siteworxpro/composer
script:
- composer run tests:phpstan

View File

@@ -0,0 +1 @@
{"version":1,"defects":[],"times":{"Siteworxpro\\Tests\\Helpers\\EnvTest::testGetReturnsStringByDefault":0.004,"Siteworxpro\\Tests\\Helpers\\EnvTest::testGetReturnsDefaultIfKeyNotSet":0,"Siteworxpro\\Tests\\Helpers\\EnvTest::testGetCastsToBoolean":0,"Siteworxpro\\Tests\\Helpers\\EnvTest::testGetCastsToInteger":0,"Siteworxpro\\Tests\\Helpers\\EnvTest::testGetCastsToFloat":0,"Siteworxpro\\Tests\\Http\\JsonResponseFactoryTest::testCreateJsonResponseReturnsValidResponse":0.002,"Siteworxpro\\Tests\\Http\\JsonResponseFactoryTest::testCreateJsonResponseHandlesEmptyData":0,"Siteworxpro\\Tests\\Http\\JsonResponseFactoryTest::testCreateJsonResponseThrowsExceptionOnInvalidData":0.001,"Siteworxpro\\Tests\\Http\\Middleware\\CorsMiddlewareTest::testAllowsConfiguredOrigin":0.015,"Siteworxpro\\Tests\\Http\\Middleware\\CorsMiddlewareTest::testBlocksUnconfiguredOrigin":0.001,"Siteworxpro\\Tests\\Http\\Middleware\\CorsMiddlewareTest::testHandlesOptionsRequest":0,"Siteworxpro\\Tests\\Http\\Middleware\\CorsMiddlewareTest::testAddsAllowCredentialsHeader":0}}

View File

@@ -1 +1,5 @@
# Template
# Template
```shell
export PHP_IDE_CONFIG=serverName=localhost
```

19
bin/pcov.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# shellcheck disable=SC2086
apk --no-cache add pcre-dev ${PHPIZE_DEPS}
git clone https://github.com/krakjoe/pcov.git
cd pcov || exec
phpize
./configure --enable-pcov
make
make install
echo "extension=pcov.so" > /usr/local/etc/php/conf.d/docker-php-ext-pcov.ini
echo "pcov.enabled=1" >> /usr/local/etc/php/conf.d/docker-php-ext-pcov.ini
echo "pcov.directory=." >> /usr/local/etc/php/conf.d/docker-php-ext-pcov.ini
# Cleanup
cd ..
rm -rf pcov

24
bin/xdebug.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env sh
echo "Installing xDebug"
apk add make gcc linux-headers autoconf alpine-sdk
curl -sL https://github.com/xdebug/xdebug/archive/3.4.0.tar.gz -o 3.4.0.tar.gz
tar -xvf 3.4.0.tar.gz
cd xdebug-3.4.0 || exit
phpize
./configure --enable-xdebug
make
make install
echo "
zend_extension=xdebug.so
xdebug.mode=debug
xdebug.start_with_request = yes
xdebug.client_host = host.docker.internal
" > /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
cd ..
rm -rf xdebug-3.4.0
rm -rf 3.4.0.tar.gz

View File

@@ -28,11 +28,14 @@
"tests:unit": [
"phpunit --colors=always --display-deprecations tests "
],
"tests:unit:coverage": [
"phpunit --colors=always --display-deprecations --coverage-html tests/reports/html tests "
],
"tests:lint": [
"phpcs ./src --standard=PSR12 --colors -v",
"phpcs ./tests --standard=PSR12 --colors -v"
],
"tests:lint-fix": [
"tests:lint:fix": [
"phpcbf ./src --standard=PSR12 --colors -v",
"phpcbf ./tests --standard=PSR12 --colors -v"
],

View File

@@ -23,9 +23,10 @@ return [
'password' => Env::get('DB_PASSWORD', 'password'),
],
'cors' => [
'allowed_origins' => Env::get('CORS_ALLOWED_ORIGINS', 'http://localhost:3000'),
'allowed_origins' => Env::get('CORS_ALLOWED_ORIGINS', 'localhost:3000'),
'allow_credentials' => Env::get('CORS_ALLOW_CREDENTIALS', true, 'bool'),
'max_age' => Env::get('CORS_MAX_AGE', 3600, 'int'),
'max_age' => Env::get('CORS_MAX_AGE', ''),
]
];

24
phpunit.xml Normal file
View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory=".phpunit.cache"
executionOrder="depends,defects"
beStrictAboutCoverageMetadata="true"
beStrictAboutOutputDuringTests="true"
displayDetailsOnPhpunitDeprecations="true"
failOnPhpunitDeprecation="true"
failOnRisky="true"
failOnWarning="true">
<testsuites>
<testsuite name="default">
<directory>tests</directory>
</testsuite>
</testsuites>
<source ignoreIndirectDeprecations="true" restrictNotices="true" restrictWarnings="true">
<include>
<directory>src</directory>
</include>
</source>
</phpunit>

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Siteworxpro\App\Facades;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\Facades\Facade;
use Siteworx\Config\Exception\EmptyDirectoryException;
use Siteworx\Config\Exception\FileNotFoundException;
@@ -21,6 +22,10 @@ use Siteworx\Config\Exception\UnsupportedFormatException;
*/
class Config extends Facade
{
protected static $cached = false;
/**
* @throws UnsupportedFormatException
* @throws FileNotFoundException
@@ -28,6 +33,17 @@ class Config extends Facade
*/
public static function getFacadeRoot(): \Siteworx\Config\Config
{
if (self::$resolvedInstance !== null) {
try {
$config = self::resolveFacadeInstance(self::getFacadeAccessor());
if ($config instanceof \Siteworx\Config\Config) {
return $config;
}
} catch (BindingResolutionException) {
}
}
return \Siteworx\Config\Config::load(__DIR__ . '/../../config.php');
}
@@ -38,6 +54,6 @@ class Config extends Facade
*/
protected static function getFacadeAccessor(): string
{
return 'config';
return \Siteworx\Config\Config::class;
}
}

View File

@@ -24,6 +24,15 @@ class Logger extends Facade
{
public static function getFacadeRoot(): RRLogger
{
if (self::$resolvedInstance !== null) {
$logger = self::resolveFacadeInstance(self::getFacadeAccessor());
if ($logger instanceof RRLogger) {
return $logger;
}
}
$rpc = RPC::create('tcp://127.0.0.1:6001');
return new RRLogger($rpc);

View File

@@ -13,10 +13,11 @@ use Nyholm\Psr7\Response;
*/
class JsonResponseFactory
{
/**
* Create a JSON response with the given data and status code.
*
* @param array $data The data to include in the response.
* @param mixed $data The data to include in the response.
* @param int $statusCode The HTTP status code for the response.
* @return Response The JSON response.
* @throws \JsonException
@@ -31,4 +32,4 @@ class JsonResponseFactory
body: json_encode($data, JSON_THROW_ON_ERROR)
);
}
}
}

View File

@@ -40,7 +40,7 @@ class CorsMiddleware implements MiddlewareInterface
$allowOrigin = in_array($origin, $allowedOrigins, true)
? $origin
: 'null';
: null;
if ($request->getMethod() === 'OPTIONS') {
$response = new Response(204);
@@ -48,6 +48,10 @@ class CorsMiddleware implements MiddlewareInterface
$response = $handler->handle($request);
}
if ($allowOrigin === null) {
return $response; // Do not add CORS headers if origin is not allowed.
}
$response = $response
->withHeader('Access-Control-Allow-Origin', $allowOrigin)
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
@@ -61,8 +65,9 @@ class CorsMiddleware implements MiddlewareInterface
$response = $response->withHeader('Access-Control-Allow-Credentials', 'true');
}
$maxAge = Config::get('CORS_MAX_AGE') !== 3600 ? Config::get('CORS_MAX_AGE') : 3600;
$maxAge = Config::get('cors.max_age') ?: '86400'; // Use correct configuration key.
return $response->withHeader('Access-Control-Max-Age', $maxAge);
}
}

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Siteworxpro\App;
use Illuminate\Container\Container;
use Illuminate\Support\Facades\Facade;
use League\Route\Http\Exception\MethodNotAllowedException;
use League\Route\Http\Exception\NotFoundException;
use League\Route\Router;
@@ -58,6 +60,9 @@ class Server
*/
private function boot(): void
{
$container = new Container();
Facade::setFacadeApplication($container);
$this->worker = new PSR7Worker(
Worker::create(),
new Psr17Factory(),

View File

@@ -16,9 +16,12 @@ class CorsMiddlewareTest extends Unit
public function testAllowsConfiguredOrigin(): void
{
Config::shouldReceive('get')
->with('CORS_ALLOWED_ORIGINS', 'https://example.com,https://another.com')
->with('cors.allowed_origins')
->andReturn('https://example.com,https://another.com');
Config::shouldReceive('get')->with('cors.allow_credentials')->andReturn(false);
Config::shouldReceive('get')->with('cors.max_age')->andReturn('');
$middleware = new CorsMiddleware();
$request = new ServerRequest('GET', '/')->withHeader('Origin', 'https://example.com');
$handler = $this->mockHandler(new Response(200));
@@ -31,7 +34,7 @@ class CorsMiddlewareTest extends Unit
public function testBlocksUnconfiguredOrigin(): void
{
Config::shouldReceive('get')
->with('CORS_ALLOWED_ORIGINS', 'https://example.com,https://another.com')
->with('cors.allowed_origins')
->andReturn('https://example.com,https://another.com');
$middleware = new CorsMiddleware();
@@ -40,14 +43,14 @@ class CorsMiddlewareTest extends Unit
$response = $middleware->process($request, $handler);
$this->assertEquals('null', $response->getHeaderLine('Access-Control-Allow-Origin'));
$this->assertEmpty($response->getHeaderLine('Access-Control-Allow-Origin'));
}
public function testHandlesOptionsRequest(): void
{
Config::shouldReceive('get')->with('CORS_ALLOWED_ORIGINS', '...')->andReturn('https://example.com');
Config::shouldReceive('get')->with('CORS_ALLOW_CREDENTIALS', 'bool')->andReturn(false);
Config::shouldReceive('get')->with('CORS_MAX_AGE')->andReturn('86400');
Config::shouldReceive('get')->with('cors.allowed_origins')->andReturn('https://example.com');
Config::shouldReceive('get')->with('cors.allow_credentials')->andReturn(false);
Config::shouldReceive('get')->with('cors.max_age')->andReturn('86400');
$middleware = new CorsMiddleware();
$request = new ServerRequest('OPTIONS', '/')->withHeader('Origin', 'https://example.com');
@@ -61,8 +64,13 @@ class CorsMiddlewareTest extends Unit
public function testAddsAllowCredentialsHeader(): void
{
Config::shouldReceive('get')->with('CORS_ALLOWED_ORIGINS', '...')->andReturn('https://example.com');
Config::shouldReceive('get')->with('CORS_ALLOW_CREDENTIALS', 'bool')->andReturn(true);
Config::shouldReceive('get')
->with('cors.allowed_origins')
->andReturn('https://example.com');
Config::shouldReceive('get')->with('cors.allowed_origins')->andReturn('https://example.com');
Config::shouldReceive('get')->with('cors.allow_credentials')->andReturn(true);
Config::shouldReceive('get')->with('cors.max_age')->andReturn('86400');
$middleware = new CorsMiddleware();
$request = new ServerRequest('GET', '/')->withHeader('Origin', 'https://example.com');
@@ -91,3 +99,4 @@ class CorsMiddlewareTest extends Unit
};
}
}

View File

@@ -4,8 +4,22 @@ declare(strict_types=1);
namespace Siteworxpro\Tests;
use Illuminate\Container\Container;
use Illuminate\Support\Facades\Facade;
use PHPUnit\Framework\TestCase;
use Siteworxpro\App\Facades\Config;
abstract class Unit extends TestCase
{
protected function setUp(): void
{
$container = new Container();
Facade::setFacadeApplication($container);
}
protected function tearDown(): void
{
Config::clearResolvedInstances();
Facade::setFacadeApplication(null);
}
}

1
tests/reports/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
html/