diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..9dc17a9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,46 @@ +name: Run Tests + +on: + push: + pull_request: + +jobs: + php-tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + php: [8.3, 8.4] + laravel: [11.*, 12.*] + dependency-version: [prefer-stable] + exclude: + - php: 8.1 + laravel: 11.* + - php: 8.1 + laravel: 12.* + - php: 8.4 + laravel: 10.* + name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.composer/cache/files + key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick + coverage: none + + - name: Install dependencies + run: | + composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction + + - name: Execute tests + run: vendor/bin/pest diff --git a/composer.json b/composer.json index 3237bb1..8c46c90 100644 --- a/composer.json +++ b/composer.json @@ -2,16 +2,16 @@ "name": "transformstudios/front", "require": { "php": "^8.3", - "laravel/framework": "^11.0", + "illuminate/contracts": "^11.0 || ^12.0", "pixelfear/composer-dist-plugin": "^0.1.4", - "statamic/cms": "^5.0" + "statamic/cms": "^5.0 || ^6.0" }, "require-dev": { "mockery/mockery": "^1.3.1", "nunomaduro/collision": "^8.1", - "phpunit/phpunit": "^11.0", - "orchestra/testbench": "^9.0", - "spatie/laravel-ray": "^1.24" + "orchestra/testbench": "^9.0 || ^10", + "spatie/laravel-ray": "^1.24", + "pestphp/pest": "^4.1" }, "autoload": { "psr-4": { @@ -43,7 +43,8 @@ }, "config": { "allow-plugins": { - "pixelfear/composer-dist-plugin": true + "pixelfear/composer-dist-plugin": true, + "pestphp/pest-plugin": true } } } diff --git a/phpunit.xml b/phpunit.xml index ecc5659..4396085 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,37 +1,21 @@ - - - - tests/Unit/ - - - ./tests - - - - - ./app - - - - - - - - - - - - - + + + + + ./tests + + + + + + + + + + + + + + diff --git a/src/Logging/LogHandler.php b/src/Logging/LogHandler.php index 2e96138..d644ad5 100644 --- a/src/Logging/LogHandler.php +++ b/src/Logging/LogHandler.php @@ -4,6 +4,7 @@ use Illuminate\Support\Collection; use Monolog\Handler\AbstractProcessingHandler; +use Monolog\Level; use Monolog\Logger as Monolog; use Monolog\LogRecord; use Statamic\Support\Arr; @@ -15,7 +16,7 @@ class LogHandler extends AbstractProcessingHandler /** @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function __construct(array $channelConfig) { - parent::__construct(Monolog::toMonologLevel($channelConfig['level'] ?? Monolog::DEBUG)); + parent::__construct(Monolog::toMonologLevel($channelConfig['level'] ?? Level::Debug)); } public function write(array|LogRecord $record): void @@ -29,13 +30,11 @@ public function write(array|LogRecord $record): void } if (! Arr::get($record, 'context.exception')) { - $errors = collect( - [ - 'Request URL: '.request()->fullUrl(), - 'Request data: '.json_encode(request()->input()), - 'Error: '.json_encode($record), - ] - ); + $errors = collect([ + 'Request URL: '.request()->fullUrl(), + 'Request data: '.json_encode(request()->input()), + 'Error: '.json_encode($record), + ]); front() ->post( @@ -60,14 +59,12 @@ private function convertErrorToFrontMessage(Throwable $error): array private function formatErrorLines(Throwable $error): Collection { - return collect( - [ - 'Request URL: '.request()->fullUrl(), - 'Request data: '.json_encode(request()->input()), - '**'.$error->getMessage().'**', - '* '.$error->getFile().' ('.$error->getLine().')', - ] - )->merge($this->formatStackTrace($error)); + return collect([ + 'Request URL: '.request()->fullUrl(), + 'Request data: '.json_encode(request()->input()), + '**'.$error->getMessage().'**', + '* '.$error->getFile().' ('.$error->getLine().')', + ])->merge($this->formatStackTrace($error)); } private function formatStackTrace(Throwable $error): Collection diff --git a/src/Logging/Logger.php b/src/Logging/Logger.php index 1457bef..a9ad6c9 100644 --- a/src/Logging/Logger.php +++ b/src/Logging/Logger.php @@ -9,9 +9,6 @@ class Logger /** @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function __invoke(array $config) { - return new Monolog( - config('app.name'), - [new LogHandler($config)] - ); + return new Monolog(config('app.name'), [new LogHandler($config)]); } } diff --git a/src/Notifications/BaseNotification.php b/src/Notifications/BaseNotification.php index dec9c01..2f6e62f 100644 --- a/src/Notifications/BaseNotification.php +++ b/src/Notifications/BaseNotification.php @@ -15,9 +15,8 @@ public function __construct( public string $key, public string $subject, public string $renderedView, - public Collection $users) - { - } + public Collection $users + ) {} public function via($notifiable) { diff --git a/src/Notifications/Channel.php b/src/Notifications/Channel.php index 850723e..f454c44 100644 --- a/src/Notifications/Channel.php +++ b/src/Notifications/Channel.php @@ -5,7 +5,6 @@ use Illuminate\Http\Client\Response; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\Http; use Statamic\Auth\User; class Channel @@ -39,17 +38,13 @@ private function data(BaseNotification $notification): array private function post(string $segment, string $id, array $data): Response { - return Http::withToken(config('front.api_token')) - ->baseUrl('https://api2.frontapp.com') + return front() ->post("/$segment/$id/messages", $data) ->throw(); } private function getConversationId(Response $response): string { - return last(explode( - '/', - Arr::get($response, '_links.related.conversation') - )); + return last(explode('/', Arr::get($response, '_links.related.conversation'))); } } diff --git a/src/helpers.php b/src/helpers.php index b3474ca..4616b14 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -5,7 +5,6 @@ function front(): PendingRequest { - return Http::withToken(config('front.api_token'))->baseUrl( - 'https://api2.frontapp.com' - ); + return Http::withToken(config('front.api_token')) + ->baseUrl('https://api2.frontapp.com'); } diff --git a/tests/Notifications/ChannelTest.php b/tests/Notifications/ChannelTest.php new file mode 100755 index 0000000..10d5663 --- /dev/null +++ b/tests/Notifications/ChannelTest.php @@ -0,0 +1,60 @@ +set('front.notifications.channel', 'test-channel'); + Http::preventStrayRequests(); + Http::fake([ + 'https://api2.frontapp.com/channels/test-channel/messages' => Http::response([ + '_links' => [ + 'related' => [ + 'conversation' => 'https://transform-studios.api.frontapp.com/conversations/cnv_id', + ], + ], + ], 200), + 'https://api2.frontapp.com/conversations/cnv_id/messages' => Http::response([], 200), + ]); +}); + +test('can send front message', function () { + $users = collect([makeUser('erin@transformstudios.com'), makeUser('erin@silentz.co')]); + $notification = new TestNotification('some-key', 'Monitor Alert: Error Detected', '', $users); + + expect((new Channel)->send(new AnonymousNotifiable, $notification))->toBeTrue(); +}); + +it('stores the conversation id', function () { + $notification = new TestNotification('some-key', 'Monitor Alert: Error Detected', '', collect([makeUser('foo@bar.com')])); + + expect(Cache::get('some-key'))->toBeNull(); + expect((new Channel)->send(new AnonymousNotifiable, $notification))->toBeTrue(); + expect(Cache::get('some-key'))->toEqual('cnv_id'); +}); + +it('removes the conversation id when alert cleared', function () { + $notification = new TestNotification('some-key', 'Monitor Alert: Error Cleared', '', collect([makeUser('foo@bar.com')])); + + Cache::put('some-key', 'cnv_id'); + expect(Cache::get('some-key'))->not->toBeNull(); + expect((new Channel)->send(new AnonymousNotifiable, $notification))->toBeTrue(); + expect(Cache::get('some-key'))->toBeNull(); +}); + +it('adds to the conversation when conversation id exists', function () { + $user = makeUser('foo@bar.com'); + $notification = new TestNotification('some-key', 'Monitor Alert: Error Detected', '', collect([$user])); + $anotherNotification = new TestNotification('some-key', 'Monitor Alert: Error Cleared', '', collect([$user])); + $channel = new Channel; + + expect($channel->send(new AnonymousNotifiable, $notification))->toBeTrue(); + expect($channel->send(new AnonymousNotifiable, $anotherNotification))->toBeTrue(); +}); + +class TestNotification extends BaseNotification {} diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..7a0bcda --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,47 @@ +extend(TransformStudios\Front\Tests\TestCase::class)->in(__DIR__); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + +expect()->extend('toBeOne', function () { + return $this->toBe(1); +}); + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ + +function makeUser(string $email): User +{ + return tap(User::make()->email($email))->save(); +} diff --git a/tests/PreventSavingStacheItemsToDisk.php b/tests/PreventSavingStacheItemsToDisk.php deleted file mode 100644 index b5e0254..0000000 --- a/tests/PreventSavingStacheItemsToDisk.php +++ /dev/null @@ -1,30 +0,0 @@ -fakeStacheDirectory = Path::tidy($this->fakeStacheDirectory); - - Stache::stores()->each(function ($store) { - $dir = Path::tidy(__DIR__ . '/__fixtures__'); - $relative = str_after(str_after($store->directory(), $dir), '/'); - $store->directory($this->fakeStacheDirectory . '/' . $relative); - }); - } - - protected function deleteFakeStacheDirectory() - { - app('files')->deleteDirectory($this->fakeStacheDirectory); - - mkdir($this->fakeStacheDirectory); - touch($this->fakeStacheDirectory . '/.gitkeep'); - } -} diff --git a/tests/TestCase.php b/tests/TestCase.php index 486c542..16eed7f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,71 +2,24 @@ namespace TransformStudios\Front\Tests; -use Orchestra\Testbench\TestCase as OrchestraTestCase; -use Statamic\Extend\Manifest; -use Statamic\Providers\StatamicServiceProvider; -use Statamic\Statamic; +use Statamic\Testing\AddonTestCase; +use Statamic\Testing\Concerns\PreventsSavingStacheItemsToDisk; use TransformStudios\Front\ServiceProvider; -abstract class TestCase extends OrchestraTestCase +abstract class TestCase extends AddonTestCase { - use PreventSavingStacheItemsToDisk; + use PreventsSavingStacheItemsToDisk; - protected function setUp(): void - { - parent::setUp(); - } - - public function tearDown(): void - { - $this->deleteFakeStacheDirectory(); + protected string $addonServiceProvider = ServiceProvider::class; - parent::tearDown(); - } - - protected function getPackageProviders($app) - { - return [ - StatamicServiceProvider::class, - ServiceProvider::class, - ]; - } + protected string $fakeStacheDirectory = __DIR__.'/__fixtures__/dev-null'; - protected function getPackageAliases($app) - { - return [ - 'Statamic' => Statamic::class, - ]; - } - - protected function getEnvironmentSetUp($app) - { - parent::getEnvironmentSetUp($app); - - $app->make(Manifest::class)->manifest = [ - 'transformstudios/front' => [ - 'id' => 'transformstudios/front', - 'namespace' => 'TransformStudios\\Front', - ], - ]; - } - - protected function resolveApplicationConfiguration($app) + protected function setUp(): void { - parent::resolveApplicationConfiguration($app); - - $configs = ['assets', 'cp', 'forms', 'routes', 'static_caching', 'sites', 'stache', 'system', 'users']; + parent::setUp(); - foreach ($configs as $config) { - $app['config']->set("statamic.$config", require __DIR__."/../vendor/statamic/cms/config/{$config}.php"); + if (! file_exists($this->fakeStacheDirectory)) { + mkdir($this->fakeStacheDirectory, 0777, true); } - - // Setting the user repository to the default flat file system - $app['config']->set('statamic.users.repository', 'file'); - - // Assume the pro edition within tests - $app['config']->set('statamic.editions.pro', true); - - $app['config']->set('front', require __DIR__ . '/../config/front.php'); } } diff --git a/tests/Unit/NotificationTest.php b/tests/Unit/NotificationTest.php deleted file mode 100755 index 3b8926d..0000000 --- a/tests/Unit/NotificationTest.php +++ /dev/null @@ -1,164 +0,0 @@ -email('erin@transformstudios.com') - ->save(); - $user2 = User::make() - ->email('erin@silentz.co') - ->save(); - - $users = collect([$user1, $user2]); - - $notification = new TestNotification([ - 'date' => Carbon::now()->toDateTimeString(), - 'event' => 'alert_raised', - 'id' => '123', - 'locations' => ['US Central'], - 'output' => 'HTTP WARNING: HTTP/1.1 403 Forbidden - 1220 bytes in 0.694 second response time', - 'subject' => 'Monitor Alert: Error Detected', - 'test' => 'Some Test', - 'type' => 'HTTP(S)', - 'users' => $users, - ]); - - $this->assertTrue((new Channel)->send(new AnonymousNotifiable, $notification)); - } - - /** @test */ - public function it_stores_the_conversation_id() - { - $user = User::make() - ->email('erin@transformstudios.com') - ->save(); - - $notification = new TestNotification([ - 'date' => Carbon::now()->toDateTimeString(), - 'event' => 'alert_raised', - 'id' => '123', - 'subject' => 'Monitor Alert: Error Detected', - 'test' => 'Some Test', - 'users' => collect([$user]), - ]); - - Http::fake(function ($request) { - return Http::response([ - '_links' => [ - 'self' => 'https://transform-studios.api.frontapp.com/messages/msg_id', - 'related' => [ - 'conversation' => 'https://transform-studios.api.frontapp.com/conversations/cnv_id', - ], - ], - ], 200); - }); - - $this->assertNull(Cache::get('123')); - $this->assertTrue((new Channel)->send(new AnonymousNotifiable, $notification)); - $this->assertEquals('cnv_id', Cache::get('123')); - } - - /** @test */ - public function it_removes_the_conversation_id_when_alert_cleared() - { - $user = User::make() - ->email('erin@transformstudios.com') - ->save(); - - $notification = new TestNotification([ - 'date' => Carbon::now()->toDateTimeString(), - 'email' => 'erin@transformstudios.com', - 'event' => 'alert_cleared', - 'id' => '123', - 'is_up' => true, - 'subject' => 'Monitor Alert: Error Detected', - 'test' => 'Some Test', - 'users' => collect([$user]), - ]); - - Http::fake(function ($request) { - return Http::response([ - '_links' => [ - 'self' => 'https://transform-studios.api.frontapp.com/messages/msg_id', - 'related' => [ - 'conversation' => 'https://transform-studios.api.frontapp.com/conversations/cnv_id', - ], - ], - ], 200); - }); - - Cache::put('123', 'cnv_123'); - $this->assertNotNull(Cache::get('123')); - $this->assertTrue((new Channel)->send(new AnonymousNotifiable, $notification)); - $this->assertNull(Cache::get('123')); - } - - /** @test */ - public function it_adds_to_the_conversation_when_conversation_id_exists() - { - $user = User::make() - ->email('erin@transformstudios.com') - ->save(); - - $notification = new TestNotification( - [ - 'date' => Carbon::now()->toDateTimeString(), - 'event' => 'alert_raised', - 'id' => '123', - 'subject' => 'Monitor Alert: Error Detected', - 'test' => 'Some Test', - 'users' => collect([$user]), - ] - ); - - $anotherNotification = new TestNotification( - [ - 'date' => Carbon::now()->toDateTimeString(), - 'event' => 'alert_cleared', - 'id' => '123', - 'subject' => 'Monitor Alert: Error Cleared', - 'test' => 'Some Test', - 'users' => collect([$user]), - ] - ); - $channel = new Channel; - - $this->assertTrue($channel->send(new AnonymousNotifiable, $notification)); - - $this->assertTrue($channel->send(new AnonymousNotifiable, $anotherNotification)); - } -} - -class TestNotification extends Notification -{ - private array $data; - - public function __construct(array $data) - { - $this->data = $data; - } - - public function toArray(): array - { - return $this->data; - } -} diff --git a/tests/__fixtures__/dev-null/.gitkeep b/tests/__fixtures__/dev-null/.gitkeep deleted file mode 100644 index e69de29..0000000