From af65ec7e96f734a8aa13269dab9b708b813cd88a Mon Sep 17 00:00:00 2001 From: edalzell Date: Tue, 13 Oct 2026 17:49:02 -0700 Subject: [PATCH 1/6] Test the Front chat config endpoint --- .../Http/Controllers/ConfigControllerTest.php | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/Http/Controllers/ConfigControllerTest.php diff --git a/tests/Http/Controllers/ConfigControllerTest.php b/tests/Http/Controllers/ConfigControllerTest.php new file mode 100644 index 0000000..918c00e --- /dev/null +++ b/tests/Http/Controllers/ConfigControllerTest.php @@ -0,0 +1,36 @@ +set('front.secret_key', 'secret'); + config()->set('front.chat.id', 'chat-id'); + config()->set('front.chat.show_on_front_end', true); +}); + +it('returns user details when configured', function () { + $user = tap(User::make()->email('erin@transformstudios.com')->set('name', 'Erin'))->save(); + + $this->actingAs($user) + ->getJson('/!/front/config') + ->assertOk() + ->assertExactJson([ + 'configured' => true, + 'chatId' => 'chat-id', + 'email' => 'erin@transformstudios.com', + 'name' => 'Erin', + 'hash' => hash_hmac('sha256', 'erin@transformstudios.com', 'secret'), + ]); +}); + +it('returns nulls for a guest', function () { + $this->getJson('/!/front/config') + ->assertOk() + ->assertJson(['configured' => true, 'email' => null, 'name' => null, 'hash' => null]); +}); + +it('is not configured when hidden on the front end', function () { + config()->set('front.chat.show_on_front_end', false); + + $this->getJson('/!/front/config')->assertOk()->assertJson(['configured' => false]); +}); From c940c7cc38af5312c7146be90c7f593847287fa8 Mon Sep 17 00:00:00 2001 From: edalzell Date: Tue, 13 Oct 2026 17:49:02 -0700 Subject: [PATCH 2/6] Remove Front logging channel --- .phpunit.cache/test-results | 1 + src/Logging/LogHandler.php | 91 ------------------------ src/Logging/Logger.php | 14 ---- src/UpdateScripts/AddToLoggingConfig.php | 30 -------- tests/Logging/LogHandlerTest.php | 51 ------------- 5 files changed, 1 insertion(+), 186 deletions(-) create mode 100644 .phpunit.cache/test-results delete mode 100644 src/Logging/LogHandler.php delete mode 100644 src/Logging/Logger.php delete mode 100644 src/UpdateScripts/AddToLoggingConfig.php delete mode 100644 tests/Logging/LogHandlerTest.php diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results new file mode 100644 index 0000000..d31594c --- /dev/null +++ b/.phpunit.cache/test-results @@ -0,0 +1 @@ +{"version":"pest_4.1.1","defects":{"P\\Tests\\Logging\\LogHandlerTest::__pest_evaluable_it_adds_the_log_context_to_the_comment":7},"times":{"P\\Tests\\Logging\\LogHandlerTest::__pest_evaluable_it_adds_the_log_context_to_the_comment":0.012,"P\\Tests\\Logging\\LogHandlerTest::__pest_evaluable_it_leaves_the_exception_out_of_the_rendered_context":0.001,"P\\Tests\\Notifications\\ChannelTest::__pest_evaluable_can_send_front_message":0.012,"P\\Tests\\Notifications\\ChannelTest::__pest_evaluable_it_stores_the_conversation_id":0.003,"P\\Tests\\Notifications\\ChannelTest::__pest_evaluable_it_removes_the_conversation_id_when_alert_cleared":0.003,"P\\Tests\\Notifications\\ChannelTest::__pest_evaluable_it_adds_to_the_conversation_when_conversation_id_exists":0.004,"P\\Tests\\Http\\Controllers\\ConfigControllerTest::__pest_evaluable_it_returns_user_details_when_configured":0.08,"P\\Tests\\Http\\Controllers\\ConfigControllerTest::__pest_evaluable_it_returns_nulls_for_a_guest":0.004,"P\\Tests\\Http\\Controllers\\ConfigControllerTest::__pest_evaluable_it_is_not_configured_when_hidden_on_the_front_end":0.004}} \ No newline at end of file diff --git a/src/Logging/LogHandler.php b/src/Logging/LogHandler.php deleted file mode 100644 index 961b66a..0000000 --- a/src/Logging/LogHandler.php +++ /dev/null @@ -1,91 +0,0 @@ -toArray(); - } - - if (! $error = Arr::get($record, 'context.exception')) { - $errors = collect([ - 'Request URL: '.request()->fullUrl(), - 'Request data: '.json_encode(request()->input()), - 'Error: '.json_encode($record), - ]); - - front() - ->post( - "/conversations/$conversation/comments", - ['body' => Str::limit($errors->implode(PHP_EOL), 10000)] - )->throw(); - - return; - } - - front() - ->post( - "/conversations/$conversation/comments", - $this->convertErrorToFrontMessage($error, Arr::except(Arr::get($record, 'context', []), 'exception')) - )->throw(); - } - - private function convertErrorToFrontMessage(Throwable $error, array $context): array - { - return ['body' => $this->formatErrorLines($error, $context)->implode(PHP_EOL)]; - } - - private function formatContext(array $context): Collection - { - return collect($context) - ->map(fn ($value, string $key) => '* '.$key.': '.(is_string($value) ? $value : json_encode($value))) - ->values(); - } - - private function formatErrorLines(Throwable $error, array $context): Collection - { - return collect([ - 'Request URL: '.request()->fullUrl(), - 'Request data: '.json_encode(request()->input()), - '**'.$error->getMessage().'**', - '* '.$error->getFile().' ('.$error->getLine().')', - ]) - ->merge($this->formatContext($context)) - ->merge($this->formatStackTrace($error)); - } - - private function formatStackTrace(Throwable $error): Collection - { - return collect($error->getTrace()) - ->take(10) - ->map(function (array $traceItem) { - if (! $file = Arr::get($traceItem, 'file')) { - return '* '.json_encode($traceItem); - } - - return '* '.$file.' ('.Arr::get($traceItem, 'line').')'; - }); - } -} diff --git a/src/Logging/Logger.php b/src/Logging/Logger.php deleted file mode 100644 index a9ad6c9..0000000 --- a/src/Logging/Logger.php +++ /dev/null @@ -1,14 +0,0 @@ -set('channels.front', [ - 'driver' => 'custom', - 'via' => \TransformStudios\Front\Logging\Logger::class, - 'level' => ConfigWriter::f()->env('FRONT_LOGGING_LEVEL', 'error'), - ])->save(); - } -} diff --git a/tests/Logging/LogHandlerTest.php b/tests/Logging/LogHandlerTest.php deleted file mode 100644 index 4ff29d3..0000000 --- a/tests/Logging/LogHandlerTest.php +++ /dev/null @@ -1,51 +0,0 @@ -set('front.logging.conversation_id', 'cnv_id'); - Http::preventStrayRequests(); - Http::fake(['https://api2.frontapp.com/conversations/cnv_id/comments' => Http::response([], 200)]); -}); - -it('adds the log context to the comment', function () { - (new LogHandler(['level' => 'error']))->write(record(context: [ - 'exception' => new Exception('boom'), - 'redis' => ['loading' => 1, 'uptime_in_seconds' => 12], - 'userId' => 7, - ])); - - Http::assertSent(function ($request) { - expect($request['body']) - ->toContain('* redis: {"loading":1,"uptime_in_seconds":12}') - ->toContain('* userId: 7'); - - return true; - }); -}); - -it('leaves the exception out of the rendered context', function () { - (new LogHandler(['level' => 'error']))->write(record(context: ['exception' => new Exception('boom')])); - - Http::assertSent(function ($request) { - expect($request['body']) - ->toContain('**boom**') - ->not->toContain('* exception:'); - - return true; - }); -}); - -function record(array $context): LogRecord -{ - return new LogRecord( - datetime: new DateTimeImmutable, - channel: 'testing', - level: Level::Error, - message: 'boom', - context: $context, - ); -} From 334953c1ba74ff7f2cdf1f7aad2c7b2ac857c7f6 Mon Sep 17 00:00:00 2001 From: edalzell Date: Tue, 13 Oct 2026 17:49:20 -0700 Subject: [PATCH 3/6] Remove Front notifications channel --- composer.json | 8 +--- src/Notifications/BaseNotification.php | 25 ----------- src/Notifications/Channel.php | 50 --------------------- src/ServiceProvider.php | 10 ----- src/helpers.php | 10 ----- tests/Notifications/ChannelTest.php | 60 -------------------------- tests/Pest.php | 33 -------------- 7 files changed, 2 insertions(+), 194 deletions(-) delete mode 100644 src/Notifications/BaseNotification.php delete mode 100644 src/Notifications/Channel.php delete mode 100644 src/helpers.php delete mode 100755 tests/Notifications/ChannelTest.php diff --git a/composer.json b/composer.json index 7bfe8d6..12281b3 100644 --- a/composer.json +++ b/composer.json @@ -3,8 +3,7 @@ "require": { "php": "^8.3", "pixelfear/composer-dist-plugin": "^0.1.4", - "statamic/cms": "^5.0 || ^6.0", - "stillat/proteus": "^4.0" + "statamic/cms": "^5.0 || ^6.0" }, "require-dev": { "mockery/mockery": "^1.3.1", @@ -16,10 +15,7 @@ "autoload": { "psr-4": { "TransformStudios\\Front\\": "src" - }, - "files": [ - "src/helpers.php" - ] + } }, "autoload-dev": { "psr-4": { diff --git a/src/Notifications/BaseNotification.php b/src/Notifications/BaseNotification.php deleted file mode 100644 index 2f6e62f..0000000 --- a/src/Notifications/BaseNotification.php +++ /dev/null @@ -1,25 +0,0 @@ -data($notification); - - if ($conversationId = Cache::pull($key = $notification->key)) { - return $this->post('conversations', $conversationId, $data)->successful(); - } - - $response = $this->post('channels', config('front.notifications.channel'), $data); - Cache::forever($key, $this->getConversationId($response)); - - return $response->successful(); - } - - private function data(BaseNotification $notification): array - { - return [ - 'body' => $notification->renderedView, - 'options' => ['archive' => false], - 'subject' => $notification->subject, - 'to' => $notification->users->map(fn (User $user) => $user->email())->all(), - ]; - } - - private function post(string $segment, string $id, array $data): Response - { - return front() - ->post("/$segment/$id/messages", $data) - ->throw(); - } - - private function getConversationId(Response $response): string - { - return last(explode('/', Arr::get($response, '_links.related.conversation'))); - } -} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index a13c003..33dd752 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -2,14 +2,11 @@ namespace TransformStudios\Front; -use Illuminate\Notifications\ChannelManager; -use Illuminate\Support\Facades\Notification; use Illuminate\Support\Facades\View; use Statamic\Facades\User as UserFacade; use Statamic\Providers\AddonServiceProvider; use Statamic\Statamic; use Statamic\Support\Arr; -use TransformStudios\Front\Notifications\Channel; class ServiceProvider extends AddonServiceProvider { @@ -37,13 +34,6 @@ public function bootAddon() // ); } - public function register() - { - Notification::resolved( - fn (ChannelManager $service) => $service->extend('front', fn ($app) => new Channel) - ); - } - private function bootScript() { if (! config('front.chat.show_on_control_panel')) { diff --git a/src/helpers.php b/src/helpers.php deleted file mode 100644 index 4616b14..0000000 --- a/src/helpers.php +++ /dev/null @@ -1,10 +0,0 @@ -baseUrl('https://api2.frontapp.com'); -} diff --git a/tests/Notifications/ChannelTest.php b/tests/Notifications/ChannelTest.php deleted file mode 100755 index 10d5663..0000000 --- a/tests/Notifications/ChannelTest.php +++ /dev/null @@ -1,60 +0,0 @@ -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 index 7a0bcda..5b9d742 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,7 +1,5 @@ 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(); -} From 8ead931044bd4d988d0a0e4b6296108aa11a1e16 Mon Sep 17 00:00:00 2001 From: edalzell Date: Tue, 13 Oct 2026 17:49:20 -0700 Subject: [PATCH 4/6] Drop unused Front config keys --- config/front.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/config/front.php b/config/front.php index 24d9c67..2ff0485 100644 --- a/config/front.php +++ b/config/front.php @@ -1,18 +1,10 @@ env('FRONT_API_TOKEN'), 'secret_key' => env('FRONT_SECRET_KEY'), 'chat' => [ 'id' => env('FRONT_CHAT_ID'), 'show_on_control_panel' => env('FRONT_SHOW_ON_CONTROL_PANEL', true), 'show_on_front_end' => env('FRONT_SHOW_ON_FRONT_END', false), ], - 'logging' => [ - 'conversation_id' => env('FRONT_CONVERSATION_ID'), - 'level' => env('FRONT_LOGGING_LEVEL', 'error'), - ], - 'notifications' => [ - 'channel' => env('FRONT_CHANNEL'), - ], ]; From 73922bf709a1e2de31b881702f73f88c0a87d86e Mon Sep 17 00:00:00 2001 From: edalzell Date: Tue, 13 Oct 2026 17:49:51 -0700 Subject: [PATCH 5/6] Ignore PHPUnit cache --- .gitignore | 2 ++ .phpunit.cache/test-results | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 .phpunit.cache/test-results diff --git a/.gitignore b/.gitignore index cdb09cc..9c9acfa 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ /composer.lock /yarn.lock /package-lock.json +/.phpunit.cache +/.phpunit.result.cache diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results deleted file mode 100644 index d31594c..0000000 --- a/.phpunit.cache/test-results +++ /dev/null @@ -1 +0,0 @@ -{"version":"pest_4.1.1","defects":{"P\\Tests\\Logging\\LogHandlerTest::__pest_evaluable_it_adds_the_log_context_to_the_comment":7},"times":{"P\\Tests\\Logging\\LogHandlerTest::__pest_evaluable_it_adds_the_log_context_to_the_comment":0.012,"P\\Tests\\Logging\\LogHandlerTest::__pest_evaluable_it_leaves_the_exception_out_of_the_rendered_context":0.001,"P\\Tests\\Notifications\\ChannelTest::__pest_evaluable_can_send_front_message":0.012,"P\\Tests\\Notifications\\ChannelTest::__pest_evaluable_it_stores_the_conversation_id":0.003,"P\\Tests\\Notifications\\ChannelTest::__pest_evaluable_it_removes_the_conversation_id_when_alert_cleared":0.003,"P\\Tests\\Notifications\\ChannelTest::__pest_evaluable_it_adds_to_the_conversation_when_conversation_id_exists":0.004,"P\\Tests\\Http\\Controllers\\ConfigControllerTest::__pest_evaluable_it_returns_user_details_when_configured":0.08,"P\\Tests\\Http\\Controllers\\ConfigControllerTest::__pest_evaluable_it_returns_nulls_for_a_guest":0.004,"P\\Tests\\Http\\Controllers\\ConfigControllerTest::__pest_evaluable_it_is_not_configured_when_hidden_on_the_front_end":0.004}} \ No newline at end of file From 3ff00c1a6eaebbeb046f59bb3df2b0859a70b61b Mon Sep 17 00:00:00 2001 From: edalzell Date: Tue, 13 Oct 2026 18:25:48 -0700 Subject: [PATCH 6/6] Require PHP 8.5 and Statamic 6 --- .github/workflows/test.yml | 14 +++----------- composer.json | 6 +++--- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9dc17a9..43f2613 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,17 +10,9 @@ jobs: strategy: matrix: os: [ubuntu-latest] - php: [8.3, 8.4] - laravel: [11.*, 12.*] + php: [8.5] 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 + name: P${{ matrix.php }} - ${{ matrix.dependency-version }} - ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 @@ -29,7 +21,7 @@ jobs: uses: actions/cache@v4 with: path: ~/.composer/cache/files - key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} + key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/composer.json b/composer.json index 12281b3..72ac428 100644 --- a/composer.json +++ b/composer.json @@ -1,14 +1,14 @@ { "name": "transformstudios/front", "require": { - "php": "^8.3", + "php": "^8.5", "pixelfear/composer-dist-plugin": "^0.1.4", - "statamic/cms": "^5.0 || ^6.0" + "statamic/cms": "^6.0" }, "require-dev": { "mockery/mockery": "^1.3.1", "nunomaduro/collision": "^8.1", - "orchestra/testbench": "^9.0 || ^10", + "orchestra/testbench": "^10.0", "spatie/laravel-ray": "^1.24", "pestphp/pest": "^4.1" },