Initial commit

This commit is contained in:
philipp lang 2024-03-16 00:08:24 +01:00
commit e747e04af6
24 changed files with 9155 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
vendor/
.phpunit.cache/

48
composer.json Normal file
View File

@ -0,0 +1,48 @@
{
"name": "zoomyboy/matrix-api",
"description": "PHP Matrix API to send a message",
"type": "library",
"require": {
"laravel/framework": "^9.0",
"guzzlehttp/guzzle": "^7.0"
},
"require-dev": {
"orchestra/testbench": "^7.0"
},
"autoload": {
"psr-4": {
"Zoomyboy\\MatrixApi\\": "src/"
}
},
"authors": [
{
"name": "Philipp Lang",
"email": "philipp@zoomyboy.de"
}
],
"autoload-dev": {
"psr-4": {
"Workbench\\App\\": "workbench/app/",
"Workbench\\Database\\Factories\\": "workbench/database/factories/",
"Workbench\\Database\\Seeders\\": "workbench/database/seeders/",
"Zoomyboy\\MatrixApi\\Tests\\": "tests/"
}
},
"scripts": {
"post-autoload-dump": [
"@clear",
"@prepare"
],
"clear": "@php vendor/bin/testbench package:purge-skeleton --ansi",
"prepare": "@php vendor/bin/testbench package:discover --ansi",
"build": "@php vendor/bin/testbench workbench:build --ansi",
"serve": [
"Composer\\Config::disableProcessTimeout",
"@build",
"@php vendor/bin/testbench serve"
],
"lint": [
"@php vendor/bin/phpstan analyse"
]
}
}

8685
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

7
config/matrix.php Normal file
View File

@ -0,0 +1,7 @@
<?php
return [
'base_url' => env('MATRIX_BASE_URL'),
'token' => env('MATRIX_TOKEN'),
'room_id' => env('MATRIX_ROOM_ID'),
];

26
phpunit.xml Normal file
View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheResultFile=".phpunit.cache/test-results"
executionOrder="depends,defects"
forceCoversAnnotation="true"
beStrictAboutCoversAnnotation="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
convertDeprecationsToExceptions="true"
failOnRisky="true"
failOnWarning="true"
verbose="true">
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
</testsuites>
<php>
<server name="MATRIX_BASE_URL" value="https://example.com"/>
<server name="MATRIX_TOKEN" value="lktzzgggg"/>
<server name="MATRIX_ROOM_ID" value="!ttggzzuu:example.com"/>
</php>
</phpunit>

56
src/Matrix.php Normal file
View File

@ -0,0 +1,56 @@
<?php
namespace Zoomyboy\MatrixApi;
use Illuminate\Support\Facades\Http;
use ReflectionClass;
use ReflectionProperty;
use Throwable;
class Matrix
{
public function __construct(private string $baseUrl, private string $roomId, private string $token)
{
}
public function message(string $message): void
{
Http::withToken($this->token)
->put("{$this->baseUrl}/_matrix/client/v3/rooms/{$this->roomParam()}/send/m.room.message/{$this->transactionId()}", [
'body' => $message,
'msgtype' => 'm.text',
]);
}
public function exception(Throwable $e): void
{
$return = '';
$publicProperties = collect((new ReflectionClass($e))->getProperties(ReflectionProperty::IS_PUBLIC));
foreach ($publicProperties as $property) {
if ($property->name === 'message') {
continue;
}
$return .= '<li><b>' . $property->getName() . ':</b> ' . $property->getValue($e) . '</li>';
}
if ($publicProperties->count()) {
$return = '<ul>' . $return . '</ul>';
}
$this->message('<p>' . $e->getMessage() . '</p>' . $return);
}
protected function roomParam(): string
{
return rawurlencode($this->roomId);
}
protected function transactionId(): string
{
return str()->random(16);
}
}

14
src/ReportsToMatrix.php Normal file
View File

@ -0,0 +1,14 @@
<?php
namespace Zoomyboy\MatrixApi;
trait ReportsToMatrix
{
public function report(): bool
{
app(Matrix::class)->exception($this);
return true;
}
}

26
src/ServiceProvider.php Normal file
View File

@ -0,0 +1,26 @@
<?php
namespace Zoomyboy\MatrixApi;
use Illuminate\Support\ServiceProvider as BaseServiceProvider;
class ServiceProvider extends BaseServiceProvider
{
public function register(): void
{
$this->mergeConfigFrom(__DIR__ . '/../config/matrix.php', 'matrix');
$this->publishes([
__DIR__ . '/../config/matrix.php' => config_path('matrix.php'),
]);
$this->app->bind(Matrix::class, function ($app) {
return new Matrix(config('matrix.base_url'), config('matrix.room_id'), config('matrix.token'));
});
}
public function boot(): void
{
}
}

21
testbench.yaml Normal file
View File

@ -0,0 +1,21 @@
providers:
# - Workbench\App\Providers\WorkbenchServiceProvider
migrations:
- workbench/database/migrations
seeders:
- Workbench\Database\Seeders\DatabaseSeeder
workbench:
start: '/'
install: true
discovers:
web: true
api: false
commands: false
components: false
views: false
build: []
assets: []
sync: []

55
tests/Fakes/HttpFake.php Normal file
View File

@ -0,0 +1,55 @@
<?php
namespace Zoomyboy\MatrixApi\Tests\Fakes;
use Illuminate\Support\Facades\Http;
class HttpFake
{
public string $roomId;
public string $baseUrl;
public function __construct()
{
$this->roomId = config('matrix.room_id');
$this->baseUrl = config('matrix.base_url');
}
public function sendsMessage(): void
{
Http::fake(function ($request) {
if ($request->method() !== 'PUT') {
return;
}
if (!str($request->url())->startsWith("{$this->baseUrl}/_matrix/client/v3/rooms/{$this->roomIdParam()}/send/m.room.message/")) {
return;
}
return Http::response(json_encode([
'event_id' => str()->random(32),
]));
});
}
public function assertMessageSent(string $message): void
{
Http::assertSent(function ($request) use ($message) {
if ($request['body'] !== $message) {
dump($message, $request['body']);
}
return $request->method() === 'PUT'
&& $request['body'] === $message
&& $request['msgtype'] === 'm.text'
&& str($request->url())->startsWith("{$this->baseUrl}/_matrix/client/v3/rooms/{$this->roomIdParam()}/send/m.room.message/");
});
}
protected function roomIdParam(): string
{
return rawurlencode($this->roomId);
}
}

27
tests/TestCase.php Normal file
View File

@ -0,0 +1,27 @@
<?php
namespace Zoomyboy\MatrixApi\Tests;
use Orchestra\Testbench\Concerns\WithWorkbench;
use Orchestra\Testbench\TestCase as TestbenchTestCase;
use Zoomyboy\MatrixApi\ServiceProvider;
use Zoomyboy\MatrixApi\Tests\Fakes\HttpFake;
class TestCase extends TestbenchTestCase
{
use WithWorkbench;
public HttpFake $httpFake;
public function setUp(): void
{
parent::setUp();
$this->httpFake = app(HttpFake::class);
}
protected function defineEnvironment($app)
{
$app->register(ServiceProvider::class);
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace Zoomyboy\MatrixApi\Tests\Unit;
use Exception;
use Illuminate\Http\Client\HttpClientException;
use Zoomyboy\MatrixApi\Matrix;
use Zoomyboy\MatrixApi\ReportsToMatrix;
use Zoomyboy\MatrixApi\Tests\TestCase;
class MessageTest extends TestCase
{
/**
* @covers Matrix
*/
public function testItSendsMessageViaMethod(): void
{
$this->httpFake->sendsMessage();
app(Matrix::class)->message('lalatt');
$this->httpFake->assertMessageSent('lalatt');
}
/**
* @covers Matrix
*/
public function testItSendsMessageWithException(): void
{
$this->httpFake->sendsMessage();
app(Matrix::class)->exception(new TestException('The error Message'));
$this->httpFake->assertMessageSent('<p>The error Message</p>');
}
/**
* @covers Matrix
*/
public function testItSendsExceptionWhenExceptionIsReported(): void
{
$this->httpFake->sendsMessage();
try {
throw new MatrixException('This is the error');
} catch (MatrixException $e) {
$e->report();
}
$this->httpFake->assertMessageSent('<p>This is the error</p>');
}
/**
* @covers Matrix
*/
public function testItSendsExceptionsWithStringParameters(): void
{
$this->httpFake->sendsMessage();
app(Matrix::class)->exception((new StringPropertyException('This is the error', '::param content::', 'third param')));
$this->httpFake->assertMessageSent('<p>This is the error</p><ul><li><b>testParam:</b> ::param content::</li><li><b>thirdParam:</b> third param</li></ul>');
}
}
class TestException extends Exception
{
}
class MatrixException extends Exception
{
use ReportsToMatrix;
}
class StringPropertyException extends HttpClientException
{
public function __construct(public $message, public string $testParam, public string $thirdParam)
{
parent::__construct($message);
}
}

2
workbench/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.env
.env.dusk

View File

View File

@ -0,0 +1,28 @@
<?php
namespace Workbench\App\Providers;
use Illuminate\Support\ServiceProvider;
class WorkbenchServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
//
}
}

View File

View File

View File

View File

@ -0,0 +1,19 @@
<?php
namespace Workbench\Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
//
}
}

View File

View File

19
workbench/routes/api.php Normal file
View File

@ -0,0 +1,19 @@
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/
// Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
// return $request->user();
// });

View File

@ -0,0 +1,19 @@
<?php
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
/*
|--------------------------------------------------------------------------
| Console Routes
|--------------------------------------------------------------------------
|
| This file is where you may define all of your Closure based console
| commands. Each Closure is bound to a command instance allowing a
| simple approach to interacting with each command's IO methods.
|
*/
// Artisan::command('inspire', function () {
// $this->comment(Inspiring::quote());
// })->purpose('Display an inspiring quote');

18
workbench/routes/web.php Normal file
View File

@ -0,0 +1,18 @@
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/
Route::get('/', function () {
return view('welcome');
});